Use immutability to solve concurrency problems

Invariant pattern

We know that concurrency is the most likely cause of thread insecurity. The main reason is that there is data competition. If multiple threads read only the same variable and do not modify it, there is no thread safety problem. Using this idea, a mode is derived — Immutability mode, also known as Immutability, which simply describes that the state of an object will not change after it is created.

Invariance class

Immutable class definition

In Java, the final modifier is used to describe an object as immutable, so immutable classes are immutable as long as all the attributes of the class are fianL modified, and only the methods are allowed to be read only. However, for the sake of rigor, the class is also required to be final modified. Because a class modified by final cannot be inherited, you can avoid other classes inheriting immutable classes and modifying their property values.

Immutability class validation

There are a lot of immutable classes in the SDK, but we may not notice that some basic classes, such as Integer, Long, Double and so on, are basically immutable classes. Here, a typical example of String to prove immutable. String I believe most people know a little bit such as String immutable, the underlying use of character array storage, etc., source code as follows.

The figure above verifies that immutable class properties are typically modified by final and classes are modified by final.

The String class property is final, but the methods are not all read-only. Replace, for example, changes the value of the variable. On the surface, this is true. We can analyze the source code as follows

public String replace(char oldChar, char newChar) { if (oldChar ! = newChar) { int len = value.length; int i = -1; Char [] val = value; If (val[I] == oldChar) {break; if (val[I] == oldChar) {break; }} if (I < len) {// The new value character array immutability is shown here !!!! char buf[] = new char[len]; // Place the value before oldChar into the array of characters 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++; } // Return new String(buf, true); } } return this; }Copy the code

We get from the source code for an immutable class if need to modify the attribute values, only to create an object will be the new attribute values assigned to new objects, so as to achieve the aim of modification, and variability of the difference is that variability is itself the value of the attribute class change, immutability class changes an attribute value will create a new object that is bound to cause the consumption of memory.

Is this a similar problem to thread creation? If new Thread() is required to create a Thread object to execute a subtask, the performance cost caused by the creation and destruction of the Thread will directly affect the application. To solve this problem, the SDK introduces the Thread pool to create multiple threads at a time, and when needed, apply to the Thread pool. You don’t need to return time to the thread pool so you can avoid the performance cost of creating and destroying threads over and over again. Does immutable class also have the pooling idea?

Of course it exists and this is one of our design patterns called the Share element pattern.

The share pattern resolves duplicate object creation

What is the share mode

Flyweight Pattern: is a software design Pattern. It uses shared objects to minimize memory usage and share information with as many similar objects as possible. It is suitable for large objects that use an unacceptably large amount of memory simply because of duplication. Usually part of the state in an object can be shared. It is common practice to put them in external data structures and pass them to the Commons when they are needed.

Generally speaking, the share mode is similar to the pooling idea. When creating an object in the share mode, check whether the object exists in the object pool first. If the object exists, use it.

The SDK already uses this idea in wrapper classes for basic data types such as Long, Integer, Short, Byte, etc. Integer, for example, does not fully use the share element pattern.

How does the SDK use the share mode

The largest range Integer can express is [-231,231-1], but there are not many commonly used numbers, so the SDK management caches the commonly used numbers between [-128,127] as shown below, which are created at JVM startup and never change. You can see the method call in the integer.valueof () method, simplified as follows

public static Integer valueOf(int i) { if (i >= -128 && i <= 127) return IntegerCache.cache[i + (128)]; return new Integer(i); } private static class IntegerCache {static final int low = -128; static final int high; static final Integer cache[]; static { high = 127; // 127 - (-128) +1 cache = new Integer[(high - low) + 1]; int j = low; For (int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }Copy the code

This explains why there is a thread safety issue with using wrapper classes of primitive data types as locks

Suppose there are two classes, A and B, which do not interfere with each other. When calling their respective setAX or setBY, they first acquire the object locks of A1 and B1, and then execute them in two threads, T1 and T2. It should be asynchronous logic.

public class Test1 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ System.out.println("==== starts calling setAX"); A a = new A(); a.setAX(); }); Threadt2 = new Thread(()->{system.out.println ("==== start call setBY"); B b = new B(); b.setBY(); }); t1.start(); t2.start(); t1.join(); t2.join(); } } class A{ Long a1=Long.valueOf(1); public void setAX() { synchronized (a1) { try { TimeUnit.SECONDS.sleep(5); System.out.println(" setAX completed "); } catch (InterruptedException e) { e.printStackTrace(); } } } } class B { Long b1=Long.valueOf(1); Public void setBY(){synchronized (b1) {system.out.println (" synchronized "); }}}Copy the code

The result is as follows: t2 does not call setBY when T1 is blocked, but is also blocked. T2 will execute setBY only when T1 releases the lock.

If you change the values of A1 and b1 to 128, out of cache, thread T2 will finish first, not wait for t1 to finish.

It is concluded that if the values of A1 and B1 are between [-128,127], the same values are taken from the cache because the share mode is adopted, so the concurrent execution of T1 and T2 threads will have mutual exclusion. If the values of A1 and B1 are not in this interval, then t1 and T2 threads will have no mutual exclusion.

Attention to invariant patterns

All properties of an object are final and immutability is not guaranteed, as shown below

If the attribute is of object type Foo, the setAge method can still modify the attribute value of Foo even if the current class Bar is immutable.

class Foo{ int age=0; String name="abc"; } final class Bar{ Foo foo; public Bar(Foo foo){ this.foo = foo; } void setAge(int age){ foo.age = age; }}Copy the code

How do I properly publish immutable classes

Immutable classes are thread-safe, but their references are not thread-safe, as shown in the following code

Foo is thread-safe for immutable classes. Bar threads are not safe and their objects reference Foo, so multithreading changes to Foo have no visibility or atomicity.

final class Foo{ final int age=0; final String name="abc"; } class Bar{ Foo foo; public void setFoo(Foo foo){ this.foo = foo; }}Copy the code

How to solve it? Atomic reference

class Bar{ AtomicReference<Foo> atomicReference = new AtomicReference<>(new Foo()); public void setFoo(Foo foo){ Foo foo1 = atomicReference.get(); atomicReference.compareAndSet(foo1,foo); }}Copy the code

The simplest invariant class

An object with an immutable class has only one state, and this state needs to be maintained by all the immutable properties in the object. There is a simpler immutable class called stateless. What is stateless? Stateless objects have no thread safety issues. Why? And it’s actually pretty simple because any variables inside a method are local variables, and local variables are thread-safe.

\