A class can be said to be thread-safe if it behaves as expected when multiple threads access it.

When is a thread unsafe?

  • Operations are not atoms. If multiple threads execute a piece of code, and the result of this code is affected by the execution timing between different threads, and the result is not expected, that is, race conditions occur, there will be thread unsafe;

    Common scenarios:

    1. count++. It contains three operations: read, modify, and write. In multi-threading, due to the different timing of the execution of the threads, it may lead to the count only increased by 1 after the execution of the two threads, but the original target really wants to increase by 1 each time.
    2. The singleton. Multiple threads may execute simultaneouslyinstance == nullSet, then create two new objects, and the original goal is to always have only one object;
    public MyObj getInstance() {if (instance == null){
           instance = new MyObj();
       }
       return instance
    }
    Copy the code

    The solution is that while the current thread is working on this code, no other thread can

    Common schemes:

    1. A single state using Java. Util. Concurrent. Atomic package some of the atomic variable classes, note if it is multiple state even if each operation is atomic, compound use not atoms;
    2. Lock. For example, synchronized is used to surround corresponding code blocks to ensure mutual exclusion between multiple threads. Attention should be paid to only include code blocks that need to be treated as atoms as far as possible.

    Reentrancy of synchronized

    Synchronized is successful when a thread attempts to acquire a lock that it already owns. Such locks are reentrant

    class Paxi {
       public synchronized  void sayHello(){
           System.out.println("hello");
       }
    }
    
    class  MyClass extends Paxi{
       public synchronized void  dosomething(){
           System.out.println("do thing ..");
           super.sayHello();
           System.out.println("over"); }}Copy the code

    Its output is zero

    do thing ..
    hello
    over
    Copy the code
  • Changes are not visible. A reader thread is not aware of a value written by another thread

    Common scenarios:

    1. Reorder. In the absence of synchronization, it is possible for the compiler, processor, runtime, and so on to adjust the order in which operations are executed, that is, to write code in a different order than the actual order in which they are executed, resulting in an invalid value being read
    2. Read variables of type long, double, etc. The JVM allows you to split a 64-bit operation into two 32-bit operations, and reading and writing in different threads can read the wrong combination of high and low bits

    Common schemes:

    1. Lock. All threads can see the latest value of the shared variable;
    2. Declare variables using the Volatile keyword. As long as the variable is written, all reads will see the change;

    Note that Volatile does not guarantee atomicity. Count++, for example, is also risky because it only guarantees that the latest value is returned when read. The advantage is that accessing Volatile variables does not lock and thus does not block the thread.

How to be thread-safe when out of sync?

  1. Thread closed. That is, data is accessed only within a single thread. There are several thread blocking techniques:
    • Ad-hoc threads are closed. This is done by writing your own programs, such as ensuring that volatile is used only on a single threadRead - modify - Write
    • The stack. All operations are generated in the stack of the thread of execution, such as a local variable in a method
    • Ref;. A separate copy of each thread and variable is maintained internally
  2. Read-only share. That is, using immutable objects.
    • Use final to decorate a field so that its “value” is immutable

      Note that final is mutable if you modify an object reference, such as set

    • Create an immutable class that contains multiple mutable data.

      Class OneValue{// Create immutable objects that cannot be modified after creation. private final BigInteger[] lastfactor; public OneValue(BigInteger i,BigInteger[] lastfactor){ this.last=i; this.lastfactor=Arrays.copy(lastfactor,lastfactor.length); } public BigInteger[] getF(BigInteger i){if(last==null || ! last.equals(i)){return null;
              }else{
                  returnArrays. Copy (lastfactor, lastfactor. Length)}}} class MyService {/ / volatile once the cache is changed, Volatile OneValue cache=new OneValue(null,null); public void handle(BigInteger i){ BigInteger[] lastfactor=cache.getF(i);if(lastfactor==null){ lastfactor=factor(i); Cache =new OneValue(I,lastfactor)} nextHandle(lastfactor)}}Copy the code

How do I construct thread-safe classes?

  1. Instance closed. Encapsulate one object into another so that all code paths that can access the encapsulated object are known, and access to the encapsulated object is thread-safe with an appropriate locking strategy.

    Used in Java Collections. SynchronizedList principle is like this. Part code is

      public static <T> List<T> synchronizedList(List<T> list) {
               return (list instanceof RandomAccess ?
                       new SynchronizedRandomAccessList<>(list) :
                       new SynchronizedList<>(list));
           }
    Copy the code

    SynchronizedList implementation, note that the mutex used here is a built-in lock

           static class SynchronizedList<E>
               extends SynchronizedCollection<E>
               implements List<E> {
               private static final long serialVersionUID = -7754090372962971524L;
       
               final List<E> list;
              public E get(int index) {
                   synchronized (mutex) {returnlist.get(index); } } public Eset(int index, E element) {
                   synchronized (mutex) {returnlist.set(index, element); } } public void add(int index, E element) { synchronized (mutex) {list.add(index, element); } } public E remove(int index) { synchronized (mutex) {returnlist.remove(index); }}}Copy the code

    The realization of the mutex

    static class SynchronizedCollection<E> implements Collection<E>, >Serializable {
        private static final long serialVersionUID = 3053995032091335093L;
        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize
        SynchronizedCollection(Collection<E> c) {
            if(c==null) throw new NullPointerException(); this.c = c; mutex = this; // mutex is actually the object itself}Copy the code

    What is monitor mode

    Java’s monitor pattern, which encapsulates all mutable state of an object and is protected by the object’s own built-in lock, is an instance closure. For example, HashTable is the monitor pattern used. Its get operation is synchronized, the built-in lock to achieve thread safety

    public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for(Entry<K,V> e = tab[index] ; e ! = null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {
                returne.value; }}return null;
    }
    Copy the code
    • Built-in lock Every object has a built-in lock. Built-in locks are also known as monitor locks. Or simply, when a monitor thread executes a synchronized modified method on an object, it automatically acquires the object’s built-in lock, releases it when the method returns, and releases it even if an exception is thrown during execution. The following two forms are equivalent:

      synchronized void myMethdo() {/ /do something
      }
      void myMethdo(){ 
          synchronized(this){
          //do somthding
          } 
          
      }
      Copy the code

      The official documentation

    • Private lock

      public class PrivateLock{ private Object mylock = new Object(); // Private lock voidmyMethod(){
              synchronized(mylock){
                  //do something
              }
          }
      }
      Copy the code

      It can also be used to protect objects. The advantage over built-in locks is that there can be more than one private lock and the client code can display the private lock

    • Class lock Modified on the STAIC method, a lock shared by all objects of a class

  2. Delegate thread-safety to thread-safe classes

If each component in a class is thread-safe, does the class handle thread-safe issues?

It depends.

  1. There is only one component, and it is thread-safe.

    public class DVT{
        private final ConcurrentMap<String,Point> locations;
        private final Map<String,Point> unmodifiableMap;
            
        public DVT(Map<String,Point> points){
            locations=new ConcurrentHashMap<String,Point>(points);
            unmodifiableMap=Collections.unmodifiableMap(locations);
            }
            
        public Map<String,Point> getLocations() {return unmodifiableMap;
            }
            
        public Point getLocation(String id){
            return locations.get(id);
            }
            
        public void setLocation(String id,int x,int y){
            if(locations.replace(id,new Point(x,y))==null){
                throw new IllegalArgumentException("invalid "+id); } } } public class Point{ public final int x,y; public Point(int x,int y){ this.x=x; this.y=y; }}Copy the code

    Thread safety analysis

    • The Point class itself cannot be changed, so it is thread-safe, and the Point method returned by the DVT is thread-safe
    • DVT’s method getLocations returns an object that is immutable and thread-safe
    • SetLocation actually operates on ConcurrentHashMap which is also thread-safe

    In summary, DVT security is in the hands of ‘locations,’ which is inherently thread-safe. DVT itself is thread-safe even though it doesn’t display any synchronization. In this case, the thread safety of the DVT is actually delegated to ‘locations,’ and the entire DVT behaves thread-safe.

  2. Thread-safety is delegated to multiple state variables. As long as the state variables are independent of each other, a combined class does not add invariance to the multiple state variables it contains. Increasing dependencies does not guarantee thread-safety

    public class NumberRange{
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);
        
        public void setLower(int I){Lower(int I)if (i>upper.get(i)){
            throw new IllegalArgumentException('can not .. ');
            }
            lower.set(i);
                
            }
                
        public void setUpper(int I){// Check before executionif(i<lower.get(i)){
            throw new IllegalArgumentException('can not .. '); } upper.set(i); }}Copy the code

    SetLower and setUpper are both ‘check before you execute’ operations, but there is not enough locking to keep the operation atomic. Assuming the original range is (0,10), a thread calling setLower(5), and an incorrect execution sequence setting setUpper(4) will likely result in (5,4).

How do I extend an existing thread-safe class?

Assume that the functionality you want to extend is’ add nothing ‘.

  1. Modify the original code directly. But there is usually no way to modify the source code
  2. Inheritance. Inherit the original code, add new functionality. However, synchronization policies are stored in two files, which can easily cause problems if the underlying synchronization policy changes
  3. Combination. Put the class into a helper class, passing the helper class’s action code. Such as expanding Collections. SynchronizedList. Pay attention to the locking mechanism, the wrong way is
    public class ListHelper<E>{ public List<E> list=Collections.synchronizedList(new ArrayList<E>()); . public synchronized boolean putIfAbsent(E x){ boolean absent = ! list.contains(x);if(absent){
                   list.add(x);
                }
                returnabsent; }}Copy the code

    PutIfAbsent does not provide thread-safety because the list’s built-in lock is not ListHelper, which means putIfAbsent is not atomic relative to other methods on the list. Collections. SynchronizedList is locked in the list itself, for the correct way

    public boolean putIfAbsent(E x){ synchronized(list){ boolean absent = ! list.contains(x);if(absent){
                list.add(x);
            }
            returnabsent; }}Copy the code

    You can also add an extra layer of locks to all classes regardless of whether they are thread-safe or not. Reference Collections. SynchronizedList method