Immutability pattern: how to use invariance to solve concurrency problems?
There is a concurrency problem when multiple threads read and write the same shared variable at the same time
If you only read, but not write, there is no concurrency problem
In fact, the simplest way to solve the concurrency problem is to let shared variables have only read operations, not write operations
Immutability mode. The so-called invariance, in short, is that once an object is created, the state will not change
Once the variable is assigned, it cannot be modified (no write operation); There is no modification operation, that is, invariance is maintained
Fast implementation of classes with immutability
- If all properties of a class are set to final and only read-only methods are allowed, then the class basically has immutability
- The class itself is final, that is, inheritance is not allowed, because the subclass can override the methods of the parent class, which may change immutability
Many classes in the Java SDK are immutable. For example, the frequently used String, Long, Integer, Double and other basic types of wrapper classes are immutable. The thread safety of these objects is guaranteed by immutability
The declarations, properties and methods of these classes strictly comply with the three requirements of immutable classes:
Classes and properties are final, and all methods are read-only
Java of String source code String This class and its properties value[]All final of replace() Method is implemented without modification value[],Instead, the replaced string is returned as a return value public final class String { private final char value[]; // Character replacement String replace(char oldChar, char newChar) { //this is returned directly without replacement if (oldChar == newChar){ return this; } int len = value.length; int i = -1; /* avoid getfield opcode */ char[] val = value; //Navigate to the character position that needs to be replaced while (++i < len) { if (val[i] == oldChar) { break; } } //oldChar not found, no replacement required if (i >= len) { return this; } //Create a buf [], which is the key //Used to save the replaced string char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } //Create a new string to return //The original string will not change return new String(buf, true); } }
Classes with immutability need to provide similar modification functions
–>
Create a new immutable object
–>
All modifications create a new immutable object
–>
Waste memory
|
|
↓
Use meta mode to avoid creating duplicate objects
Flyweight Pattern
–>
You can reduce the number of objects created, thereby reducing memory usage
–>
The wrapper classes of basic data types such as Long, Integer, Short and Byte in the Java language use the meta pattern
Meta sharing mode is actually an object pool:
Before creating, first go to the object pool to see if it exists;
If it already exists, use the objects in the object pool;
If it does not exist, a new object will be created and put into the object pool
Long valueOf(long l) { final int offset = 128; // [- 128127] direct numbers are cached if (l >= -128 && l <= 127) { return LongCache.cache[(int) l + offset]; } return new Long(l); } // Long internally maintains a cache, which is equivalent to a static object pool // Only numbers between [- 128127] are cached because of the highest number utilization // There are 2 ^ 64 states of the Long object. There are too many states to cache all static class LongCache { static final Long cache[] = new Long[-(-128) + 127 + 1]; static { for (int i = 0; i < cache.length; i++) { cache[i] = new Long(i - 128); } } }
be careful:
Integer and String objects are not suitable for locking
Basically, all basic types of wrapper classes are not suitable for locks
Because they use the meta sharing mode internally, it will lead to locks that look private but are actually common
Sample code: actually al and bl Is an object, the result A and B Shared is a lock class A { Long al=Long.valueOf(1); public void setAX(){ synchronized (al) { //Omit countless codes } } } class B { Long bl=Long.valueOf(1); public void setBY(){ synchronized (bl) { //Omit countless codes } } }
Precautions for using Immutability mode
Note the following two points:
- All attributes of an object are final and cannot be guaranteed to be immutable;
- Immutable objects also need to be published correctly
stay Java In language, final Once the modified attribute is assigned, it cannot be modified, However, if the type of the attribute is an ordinary object, the attribute of the ordinary object can be modified. class Foo{ int age=0; int name="abc"; } final class Bar { final Foo foo; -- final, Can still pass setAge() Method to set foo Properties of age void setAge(int a){ foo.age=a; } }
When using Immutability mode, you must confirm where the boundary of invariance is and whether the attribute object is required to be immutable
If you only need foo to maintain visibility -- > use volatile decoration
You need to ensure atomicity -- > which can be implemented through atomic classes -- > to solve the atomicity problem of immutable object references
public class SafeWM { final AtomicReference<WMRange> rf = new AtomicReference<>(new WMRange(0, 0)); // Set inventory ceiling void setUpper(int v) { while (true) { WMRange or = rf.get(); // Check the validity of parameters if (v < or.lower) { throw new IllegalArgumentException(); } WMRange nr = new WMRange(v, or.lower); if (rf.compareAndSet(or, nr)) { return; } } } class WMRange { final int upper; final int lower; WMRange(int upper, int lower) { this.upper = upper; this.lower = lower; } } }
summary
String, Long, Integer, Double and other basic types of wrapper classes in the Java language are immutable,
Thread safety of these objects is guaranteed by immutability
An object with invariance has only one state, which is determined by all invariant attributes inside the object.
In fact, there is a simpler invariant object, which is stateless.
Stateless objects have no properties but only methods.
In addition to stateless objects, you may have heard of stateless services, stateless protocols, and so on.
Statelessness has many benefits, and the core point is performance.
In the field of multithreading, stateless objects have no thread safety problems, do not need synchronous processing, and have good natural performance;
In the distributed domain, statelessness means that it can expand horizontally indefinitely, so the performance bottleneck in the distributed domain must not lie in the stateless service nodes.
reflection
The attribute is final and only the get method. Does this class have immutability?
public final class Account { private final StringBuffer user; public Account(String user) { this.user = new StringBuffer(user); } public StringBuffer getUser() { return this.user; } @Override public String toString() { return "user" + user; } } Some answers: If the property of the class is a reference type, The class corresponding to this attribute also needs to meet the condition of immutable class, And you cannot provide a method to modify this property StringBuffer Properties of class value Is variable String Class value definition:private final char value[]; StringBuffer Class value definition:char[] value; And provides append(Object object)and setCharAt(int index, char ch)modify value therefore,Account Class does not have immutability This code should be thread safe, but it is not immutable mode. StringBuffer Only the field reference is immutable, and the value can be called StringBuffer The method of change, This field needs to be changed to String Such immutable objects are solved.
Copy on write mode: COW that is not a delay policy
Copy on write is often abbreviated as CoW or CoW. As the name suggests, it is copy on write