[Flying Yuan Mode] of Design Patterns, Shared Bikes Are Popular for No Reason

1. What is Flyweight Mode

Flyweight Pattern (Flyweight Pattern) is also called Flyweight Pattern, also known as Lightweight Pattern, which is 对象池an implementation of Flyweight Pattern. Similar to the thread pool, the thread pool can avoid creating and destroying multiple objects continuously and consuming performance. Provides a way to reduce the number of objects and thereby improve the object structure required by the application. Its purpose is to share fine-grained objects and centralize the access of multiple objects, rather than creating a separate object for each visitor, so as to reduce memory consumption, which belongs to the structural mode.

Object-oriented technology can solve some flexibility or scalability problems very well, but in many cases it needs to increase the number of classes and objects in the system. When the number of objects is too large, it will lead to high running costs and bring about problems such as performance degradation. Flyweight mode was born to solve this kind of problems.

Flyweight mode divides the state of an object into internal state and external state. The internal state is unchanged and the external state is changing; then, it 通过共享不变的部分achieves the purpose of reducing the number of objects and saving memory.

The essence of Flyweight mode is to cache shared objects and reduce memory consumption.

1. The role of Flyweight mode

insert image description here

The Flyweight mode mainly has the following roles:

  • Abstract flyweight role (Flyweight): usually an interface or an abstract class, which declares the public methods of the specific flyweight class in the abstract flyweight class. These methods can provide the internal data (internal state) of the flyweight object to the outside world, and at the same time External data (external state) can also be set via these methods.
  • Concrete Flyweight role: It implements the abstract flyweight class, called a flyweight object; it provides storage space for the internal state in the concrete flyweight class. Usually we can combine the singleton mode to design specific flyweight classes, and provide unique flyweight objects for each specific flyweight class. The internal state processing of this role should have nothing to do with the environment, and there cannot be an operation that changes the internal state and modifies the external state at the same time.
  • Unsharable Flyweight role: Not all subclasses of the abstract flyweight class need to be shared, subclasses that cannot be shared can be designed as non-shared concrete flyweight classes; when a non-shared concrete flyweight class is required Objects can be created directly by instantiation.
  • Flyweight Factory role: responsible for creating and managing flyweight roles. When a customer object requests a flyweight object, the flyweight factory checks whether there is a flyweight object that meets the requirements in the system, and if it exists, it will be provided to the client; if it does not exist, a new flyweight object will be created.

2. Flyweight mode application scenarios

When the same set of information is needed in multiple places in the system, the information can be encapsulated into an object, and then the object can be cached, so that one object can be provided to multiple places that need to be used, avoiding multiple times of a large number of the same object Created, consumes a lot of memory space.

Flyweight mode is actually an improved mechanism of factory mode. Flyweight mode also requires the creation of one or a group of objects, and the object is generated through the factory method, but the Flyweight mode adds the function of caching to the factory method. The main summary is the following application scenarios:

  1. A system has a large number of identical or similar objects, resulting in a large amount of memory consumption.
  2. Most of the state of an object can be externalized, and these external states can be passed into the object.
  3. When using the Flyweight mode, it is necessary to maintain a Flyweight pool for storing the Flyweight objects, which consumes certain system resources. Therefore, it is worth using the Flyweight mode when the Flyweight objects need to be reused many times.

3. Internal state and external state of Flyweight mode

The definition of Flyweight mode puts forward two requirements for us: fine-grained and shared objects. Because fine-grained objects are required, it is inevitable that there will be a large number of objects with similar properties. At this time, we divide the information of these objects into two parts: internal state and external state.

  1. The internal state, that is, the shareable part that does not change as the environment changes, is the information shared by the object.
  2. External state refers to the non-shareable part that changes as the environment changes.

The essentials of Flyweight mode is 区分应用中的这两种状态,并将外部状态外部化.

For example, the connection object in the connection pool, the user name, password, connection url and other information stored in the connection object are set when the object is created, and will not change with the environment. These are internal states. When each connection is to be recycled, we need to mark it as available, these are external states.

4. Advantages and disadvantages of Flyweight mode

advantage:

  • Reduce the creation of objects, reduce the number of objects in memory, reduce system memory, and improve efficiency;
  • Reduce resource usage other than memory.

shortcoming:

  • Pay attention to internal and external states, and thread safety issues;
  • Complicate the logic of the system and program.

5. The difference between flyweight mode and singleton

In the singleton mode, a class can only create one object, while in the Flyweight mode, a class can create multiple objects, and each object is shared by multiple code references. In fact, the Flyweight pattern is somewhat similar to a singleton variant: 多例.

In fact, to distinguish between the two design patterns, we can't just look at the code implementation, but look at the design intent, that is, the problem to be solved. Although there are many similarities between flyweight mode and multiple instances from the point of view of code implementation, they are completely different from the point of view of design intent. apply 享元模式是为了对象复用,节省内存, and apply 单例模式是为了限制对象的个数.

6. The difference between flyweight mode and cache

In the implementation of the Flyweight pattern, we use the factory class to "cache" the created objects. The "cache" here actually means "storage", which is different from what we usually call "database cache", "CPU cache" and "MemCache cache". The cache we usually talk about is mainly to improve access efficiency, not reuse.

7. Precautions and details of Flyweight mode

  1. The Flyweight pattern is understood in this way: "Share" means sharing, and "Yuan" means an object.
  2. When there are a large number of objects in the system, these objects consume a lot of memory, and most of the state of the objects can be externalized, we can consider using the Flyweight mode.
  3. Judging by the unique identification code, if it exists in the memory, return the object identified by the unique identification code and store it in HashMap/HashTable.
  4. The Flyweight mode greatly reduces the creation of objects, reduces the memory usage of the program, and improves efficiency.
  5. Flyweight mode increases the complexity of the system. It is necessary to separate the internal state from the external state, and the external state has solidification characteristics and should not change with the change of the internal state. This is what we need to pay attention to when using the Flyweight pattern.
  6. When using the flyweight pattern, pay attention to the division of internal state and external state, and a factory class is required to control it.
  7. The classic application scenarios of Flyweight mode are scenarios that require buffer pools, such as String constant pools, database connection pools, etc.

Two, examples

1. General writing method of Flyweight mode

// 抽象享元角色
public interface IFlyweight {
    
    
    void operation(String extrinsicState);
}
// 具体享元角色
public class ConcreteFlyweight implements IFlyweight {
    
    
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
    
    
        this.intrinsicState = intrinsicState;
    }


    public void operation(String extrinsicState) {
    
    
        System.out.println("Object address: " + System.identityHashCode(this));
        System.out.println("IntrinsicState: " + this.intrinsicState);
        System.out.println("ExtrinsicState: " + extrinsicState);
    }
}
// 享元工厂
public class FlyweightFactory {
    
    
    private static Map<String, IFlyweight> pool = new HashMap<String, IFlyweight>();

    // 因为内部状态具备不变性,因此作为缓存的键
    public static IFlyweight getFlyweight(String intrinsicState) {
    
    
        if (!pool.containsKey(intrinsicState)) {
    
    
            IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
            pool.put(intrinsicState, flyweight);
        }
        return pool.get(intrinsicState);
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        IFlyweight flyweight1 = FlyweightFactory.getFlyweight("aa");
        IFlyweight flyweight2 = FlyweightFactory.getFlyweight("bb");
        flyweight1.operation("a");
        flyweight2.operation("b");
    }
}

2. Case of Tetris

The picture below is a block in the well-known Tetris. If in the Tetris game, each different block is an instance object, these objects will take up a lot of memory space. The Flyweight mode is used below to implement .
insert image description here
Take a look at the class diagram:
insert image description here
Tetris has different shapes, and we can extract AbstractBox from these shapes to define common properties and behaviors.

// 抽象
public abstract class AbstractBox {
    
    
	public abstract String getShape();
		public void display(String color) {
    
    
		System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
	}
}

Define different shapes, IBox class, LBox class, OBox class, etc.

public class IBox extends AbstractBox {
    
    
	@Override
	public String getShape() {
    
    
		return "I";
	}
}
public class LBox extends AbstractBox {
    
    
	@Override
	public String getShape() {
    
    
		return "L";
	}
}
public class OBox extends AbstractBox {
    
    
	@Override
	public String getShape() {
    
    
		return "O";
	}
}

A factory class (BoxFactory) is provided to manage flyweight objects (that is, AbstractBox subclass objects). Only one factory class object is needed, so the singleton mode can be used. And provide the factory class with a method to get the shape.

public class BoxFactory {
    
    
	private static HashMap<String, AbstractBox> map;

	private BoxFactory() {
    
    
		map = new HashMap<String, AbstractBox>();
		AbstractBox iBox = new IBox();
		AbstractBox lBox = new LBox();
		AbstractBox oBox = new OBox();
		map.put("I", iBox);
		map.put("L", lBox);
		map.put("O", oBox);
	}
	public static final BoxFactory getInstance() {
    
    
		return SingletonHolder.INSTANCE;
	}
	private static class SingletonHolder {
    
    
		private static final BoxFactory INSTANCE = new BoxFactory();
	}
	public AbstractBox getBox(String key) {
    
    
		return map.get(key);
	}
}

3. Ticketing business case

There will be train ticket swiping software during the New Year and holidays. The ticket swiping software will cache the information we fill in, and then check the remaining ticket information regularly. When grabbing tickets, we must check whether there is any ticket information we need. Here we assume that the information of a train includes: departure station, destination station, price, seat category. Now it is required to write a function for querying train tickets, and the information of relevant tickets can be found through the departure station and destination station.

public interface ITicket {
    
    
    void showInfo(String bunk);
}
// 具体票
public class TrainTicket implements ITicket {
    
    
    private String from;
    private String to;
    private int price;

    public TrainTicket(String from, String to) {
    
    
        this.from = from;
        this.to = to;
    }


    public void showInfo(String bunk) {
    
    
        this.price = new Random().nextInt(500);
        System.out.println(String.format("%s->%s:%s价格:%s 元", this.from, this.to, bunk, this.price));
    }
}
// 抢票工厂
public class TicketFactory {
    
    
    private static Map<String, ITicket> sTicketPool = new ConcurrentHashMap<String,ITicket>();

    public static ITicket queryTicket(String from, String to) {
    
    
        return new TrainTicket(from, to);
    }
}

Write client code:

public static void main(String[] args) {
    
    
    ITicket ticket = TicketFactory.queryTicket("北京西", "长沙");
    ticket.showInfo("硬座");
    ticket = TicketFactory.queryTicket("北京西", "长沙");
    ticket.showInfo("软座");
    ticket = TicketFactory.queryTicket("北京西", "长沙");
    ticket.showInfo("硬卧");
}

In the above code, we found that when the client makes a query, the system directly creates a train ticket object through TicketFactory. In this way, if a large number of users request the same ticket information at a certain moment, a large number of train ticket objects will be created, and the pressure on the system will increase suddenly. . A better way is to use cache and reuse ticket objects:

// 使用缓存
public class TicketFactory {
    
    
    private static Map<String, ITicket> sTicketPool = new ConcurrentHashMap<String,ITicket>();

    public static ITicket queryTicket(String from, String to) {
    
    
        String key = from + "->" + to;
        if (TicketFactory.sTicketPool.containsKey(key)) {
    
    
            System.out.println("使用缓存:" + key);
            return TicketFactory.sTicketPool.get(key);
        }
        System.out.println("首次查询,创建对象: " + key);
        ITicket ticket = new TrainTicket(from, to);
        TicketFactory.sTicketPool.put(key, ticket);
        return ticket;
    }
}

Among them, ITicket is an abstract flyweight role, TrainTicket is a specific flyweight role, and TicketFactory is a flyweight factory. In fact, this method is very similar to the registered singleton mode.

4. Database connection pool case

We often use the database connection pool, because when we use the Connection object, the main performance consumption is when the connection is established and closed. In order to improve the performance of the Connection when calling, we create the Connection object before calling and cache it. Take it from the cache, put it back when it is used up, and achieve the purpose of resource reuse.

public class ConnectionPool {
    
    

    private Vector<Connection> pool;

    private String url = "jdbc:mysql://localhost:3306/test";
    private String username = "";
    private String password;
    private String driverClassName = "com.mysql.jdbc.Driver";
    private int poolSize = 100;

    public ConnectionPool() {
    
    
        pool = new Vector<Connection>(poolSize);

        try{
    
    
            Class.forName(driverClassName);
            for (int i = 0; i < poolSize; i++) {
    
    
                Connection conn = DriverManager.getConnection(url,username,password);
                pool.add(conn);
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

    }

    public synchronized Connection getConnection(){
    
    
        if(pool.size() > 0){
    
    
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        }
        return null;
    }

    public synchronized void release(Connection conn){
    
    
        pool.add(conn);
    }
}

public static void main(String[] args) {
    
    
    ConnectionPool pool = new ConnectionPool();
    Connection conn = pool.getConnection();
    System.out.println(conn);
}

Connection pools like this are widely used in development frameworks to improve performance.

3. Flyweight mode in the source code

1. String constant pool

For more string constant pools, please go to:
String's Intern() method, explain the string constant pool in detail!

Java defines the String class as final (unchangeable). Strings in the JVM are generally stored in the string constant pool. Java will ensure that there is only one copy of a string in the constant pool. This string constant pool was before JDK6.0 It is located in the permanent generation, and in JDK7.0, the JVM takes it out of the permanent generation and places it in the heap.

String s1 = "hello";
String s2 = "hello";
String s3 = "he" + "llo";
String s4 = "hel" + new String("lo");
String s5 = new String("hello");
String s6 = s5.intern();
String s7 = "h";
String s8 = "ello";
String s9 = s7 + s8;
// 由于s2指向的字面量 hello 在常量池中已经存在了(s1先于s2),于是JVM就返回这个字面量绑定的引用,所以s1 == s2
System.out.println(s1==s2);//true
// s3字面量的拼接其实就是 hello ,JVM在编译期间就已经做了优化,所以s1 == s3
System.out.println(s1==s3);//true
// new String("lo")生成了两个对象,lo和String("lo"),lo存在于字符串常量池中,String("lo")存在于堆中,s4其实是两个对象的相加,编译器不会进行优化,相加的结果存在堆中,而s1存在字符串常量池中,不相等
System.out.println(s1==s4);//false
// 同上
System.out.println(s1==s9);//false
// 都在堆中
System.out.println(s4==s5);//false
// intern方法能使一个位于堆中的字符串在运行期间动态地加入到字符串常量池中,并返回字符串常量池的引用
System.out.println(s1==s6);//true

When creating a String variable in the form of a literal, the JVM will put the literal "hello" into the string constant pool during compilation, and it will be loaded into the memory when the Java program starts. The characteristic of this string constant pool is that there is one and only one identical literal. If there are other identical literals, the JVM will return a reference to this literal. If there is no identical literal, it will be created in the string constant pool. literal and returns a reference to it.

2. Integer constant pool

Integer a = Integer.valueOf(100);
Integer b = 100;

Integer c = Integer.valueOf(1000);
Integer d = 1000;

System.out.println("a==b:" + (a==b)); // true
System.out.println("c==d:" + (c==d)); // false

Integer's valueOf method defaults to get it from the cache first:

static final int low = -128;
static final int high = 127; // 静态代码块初始化的

@IntrinsicCandidate
public static Integer valueOf(int i) {
    
    
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}


static {
    
    
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
        VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
    
    
        try {
    
    
            h = Math.max(parseInt(integerCacheHighPropValue), 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
        } catch( NumberFormatException nfe) {
    
    
            // If the property cannot be parsed into an int, ignore it.
        }
    }
    high = h;

    // Load IntegerCache.archivedCache from archive, if possible
    CDS.initializeFromArchive(IntegerCache.class);
    int size = (high - low) + 1;

    // Use the archived cache if it exists and is large enough
    if (archivedCache == null || size > archivedCache.length) {
    
    
        Integer[] c = new Integer[size];
        int j = low;
        for(int i = 0; i < c.length; i++) {
    
    
            c[i] = new Integer(j++);
        }
        archivedCache = c;
    }
    cache = archivedCache; // 初始化缓存
    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
}

We found that the valueOf method of the Integer source code makes a judgment. If the target value is between -128 and 127, the cache is taken directly, otherwise a new object is created. why? Because data between -128 and 127 is most frequently used in the int range, in order to save memory consumption caused by frequent object creation, the Flyweight mode is used here to improve performance.

3. Long constant pool

Similarly, Long also has caching, but the maximum value cannot be specified:

@IntrinsicCandidate
public static Long valueOf(long l) {
    
    
    final int offset = 128;
    if (l >= -128 && l <= 127) {
    
     // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

References

http://www.uml.org.cn/sjms/202105262.asp

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/130663144