Concurrent programming learning notes -- Concurrent Design Pattern

Posted by kaser on Sat, 01 Jan 2022 23:00:07 +0100

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
  1. If all properties of a class are set to final and only read-only methods are allowed, then the class basically has immutability
  2. 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:

  1. All attributes of an object are final and cannot be guaranteed to be immutable;
  2. 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

Topics: Concurrent Programming