Five ways to synchronize multiple threads in Java

  • 2020-04-01 04:11:58
  • OfStack

Why thread synchronization

Because when we have multiple threads trying to access a variable or object at the same time, if there are both reads and writes in those threads, the state of the variable's value or object will be confused, resulting in a program exception. For example, if a bank account is operated by two threads at the same time, one to withdraw 100 pieces and one to deposit 100 pieces. Suppose the account originally had 0 pieces, what would happen if the withdrawal thread and the deposit thread happened at the same time? If the withdrawal is unsuccessful, the account balance is 100. If the withdrawal is successful, the account balance is 0. Which is it exactly? It's hard to say. So that's what multithreading is all about.

The code when out of sync


Bank.java 
 
package threadTest; 
 
 
public class Bank { 
 
 private int count =0;//The account balance
 
 //To save money
 public void addMoney(int money){ 
  count +=money; 
  System.out.println(System.currentTimeMillis()+" Deposit: "+money); 
 } 
 
 //To withdraw money
 public void subMoney(int money){ 
  if(count-money < 0){ 
   System.out.println(" Lack of balance "); 
   return; 
  } 
  count -=money; 
  System.out.println(+System.currentTimeMillis()+" Remove: "+money); 
 } 
 
 //The query
 public void lookMoney(){ 
  System.out.println(" Account balance: "+count); 
 } 
} 
 
SyncThreadTest.java 
 
package threadTest; 
 
public class SyncThreadTest { 
 
 public static void main(String args[]){ 
  final Bank bank=new Bank(); 
 
  Thread tadd=new Thread(new Runnable() { 
 
   @Override 
   public void run() { 
    // TODO Auto-generated method stub 
    while(true){ 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     bank.addMoney(100); 
     bank.lookMoney(); 
     System.out.println("n"); 
 
    } 
   } 
  }); 
 
  Thread tsub = new Thread(new Runnable() { 
 
   @Override 
   public void run() { 
    // TODO Auto-generated method stub 
    while(true){ 
     bank.subMoney(100); 
     bank.lookMoney(); 
     System.out.println("n"); 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     }  
    } 
   } 
  }); 
  tsub.start(); 
 
  tadd.start(); 
 } 
 
} 

The code is so simple, I'm not going to explain it, but let's see what happens. Cut out a part of it, is not very messy, there is writing can not understand.

Insufficient balance  
Account balance: 0

Insufficient balance  
Account balance: 100

1441790503354
Account balance: 100

1441790504354 deposited: 100 
Account balance: 100

Take out: 100 
Account balance: 100

1441790505355 deposited: 100 
Account balance: 100

Take out: 100 
Account balance: 100

Two, when the use of synchronization code

(1) synchronization method:

Methods modified with the synchronized keyword. Because every object in Java has a built-in lock, when you modify a method with this keyword, the built-in lock protects the entire method. Before calling this method, you need to acquire the built-in lock or you will be blocked.

Modified bank.java

Now look at the results:

Insufficient balance  
Account balance: 0

Insufficient balance  
Account balance: 0

1441790837380 deposited: 100 
Account balance: 100

Take out: 100 
Account balance: 0 
1441790838380 deposited: 100 
Account balance: 100

Take out: 100 
Account balance: 0

Instantly it feels understandable.

Note: the synchronized keyword can also modify a static method, which, if called, will lock the entire class

(2) synchronize code blocks

A block of statements decorated with the synchronized keyword. Blocks of statements decorated by the keyword are automatically locked to synchronize

The bank. Java code is as follows:


package threadTest; 
 
 
public class Bank { 
 
 private int count =0;//The account balance
 
 //To save money
 public void addMoney(int money){ 
 
  synchronized (this) { 
   count +=money; 
  } 
  System.out.println(System.currentTimeMillis()+" Deposit: "+money); 
 } 
 
 //To withdraw money
 public void subMoney(int money){ 
 
  synchronized (this) { 
   if(count-money < 0){ 
    System.out.println(" Lack of balance "); 
    return; 
   } 
   count -=money; 
  } 
  System.out.println(+System.currentTimeMillis()+" Remove: "+money); 
 } 
 
 //The query
 public void lookMoney(){ 
  System.out.println(" Account balance: "+count); 
 } 
} 

The operation results are as follows:

Insufficient balance  
Account balance: 0

1441791806699
Account balance: 100

Draw out: 100& PI;
Account balance: 0

1441791807699
Account balance: 100

The effect is similar to method 1.

Note: synchronization is an expensive operation, so it should be minimized. There is usually no need to synchronize the entire method, just the key code using synchronized code blocks.

(3) use special field variables (Volatile) to achieve thread synchronization

The a.vatile keyword provides a lock-free mechanism for accessing domain variables
B. Using volatile to modify a field tells the virtual machine that the field may be updated by another thread
C. Therefore, every time the field is used, it is recalculated instead of using the value in the register
D.vatile does not provide any atomic operations, nor can it be used to modify variables of final type

The bank. Java code is as follows:


package threadTest; 
 
 
public class Bank { 
 
 private volatile int count = 0;//The account balance
 
 //To save money
 public void addMoney(int money) { 
 
  count += money; 
  System.out.println(System.currentTimeMillis() + " Deposit: " + money); 
 } 
 
 //To withdraw money
 public void subMoney(int money) { 
 
  if (count - money < 0) { 
   System.out.println(" Lack of balance "); 
   return; 
  } 
  count -= money; 
  System.out.println(+System.currentTimeMillis() + " Remove: " + money); 
 } 
 
 //The query
 public void lookMoney() { 
  System.out.println(" Account balance: " + count); 
 } 
} 

How does it work?

Insufficient balance  
Account balance: 0

Insufficient balance  
Account balance: 100

1441792010959 deposited: 100 
Account balance: 100

Withdrawal: 100 
Account balance: 0

1441792011961 deposited: 100& cake;
Account balance: 100

Is it not to understand, and confusion. Why is that? Because volatile does not guarantee atomic operations, volatile cannot replace synchronized. In addition, volatile organizes the compiler to optimize the code, so you can't use it without using it. The idea is that each time a thread accesses a variable decorated with volatile, it reads from memory, not from the cache, so that each thread accesses the same value. This ensures synchronization.

(4) use reentrant lock to achieve thread synchronization

A java.util.concurrent package has been added in JavaSE5.0 to support synchronization. The ReentrantLock class is a reentrant, mutually exclusive Lock that implements the Lock interface, has the same basic behavior and semantics as using the synchronized method and fast, and extends its capabilities.
Common methods of the ReenreantLock class are:
ReentrantLock() : creates an instance of ReentrantLock
Lock () : get the lock
Unlock () : to release a lock
Note: ReentrantLock() also has a constructor that can create a fair lock, but is not recommended because it dramatically reduces program performance
The Bank. Java code is modified as follows:


package threadTest; 
 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 
 
 
public class Bank { 
 
 private int count = 0;//The account balance
 
 //The lock needs to be declared
 private Lock lock = new ReentrantLock(); 
 
 //To save money
 public void addMoney(int money) { 
  lock.lock();//locked
  try{ 
  count += money; 
  System.out.println(System.currentTimeMillis() + " Deposit: " + money); 
 
  }finally{ 
   lock.unlock();//unlock
  } 
 } 
 
 //To withdraw money
 public void subMoney(int money) { 
  lock.lock(); 
  try{ 
 
  if (count - money < 0) { 
   System.out.println(" Lack of balance "); 
   return; 
  } 
  count -= money; 
  System.out.println(+System.currentTimeMillis() + " Remove: " + money); 
  }finally{ 
   lock.unlock(); 
  } 
 } 
 
 //The query
 public void lookMoney() { 
  System.out.println(" Account balance: " + count); 
 } 
} 

How does it work?

Insufficient balance  
Account balance: 0

Insufficient balance  
Account balance: 0

1441792891934 deposited: 100& cake;
Account balance: 100

1441792892935 deposited: 100 
Account balance: 200

Withdrawal: 100 
Account balance: 100

The effect is similar to the previous two methods.

If the synchronized keyword satisfies the user's needs, it is used because it simplifies the code. If you need more advanced functionality, use the ReentrantLock class, where you pay attention to releasing the lock in time, otherwise a deadlock occurs, usually in the finally code

(5) use local variables to achieve thread synchronization

The bank. Java code is as follows:


package threadTest; 
 
 
public class Bank { 
 
 private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ 
 
  @Override 
  protected Integer initialValue() { 
   // TODO Auto-generated method stub 
   return 0; 
  } 
 
 }; 
 
 //To save money
 public void addMoney(int money) { 
  count.set(count.get()+money); 
  System.out.println(System.currentTimeMillis() + " Deposit: " + money); 
 
 } 
 
 //To withdraw money
 public void subMoney(int money) { 
  if (count.get() - money < 0) { 
   System.out.println(" Lack of balance "); 
   return; 
  } 
  count.set(count.get()- money); 
  System.out.println(+System.currentTimeMillis() + " Remove: " + money); 
 } 
 
 //The query
 public void lookMoney() { 
  System.out.println(" Account balance: " + count.get()); 
 } 
} 

Operation effect:

Insufficient balance  
Account balance: 0

Insufficient balance  
Account balance: 0

1441794247939 deposited: 100 
Account balance: 100

Insufficient balance  
1441794248940
Account balance: 0

Account balance: 200

Insufficient balance  
Account balance: 0

1441794249941 deposited: 100 
Account balance: 300

Looked at the running effect, at the beginning of a confused water, how only to save, not to take ah? Look at how ThreadLocal works:

If a ThreadLocal management variable is used, each thread that USES the variable gets a copy of the variable, independent of each other, so that each thread can modify its variable copy at will without affecting other threads. Now you see, each thread is running a copy, which means that the deposit and the withdrawal are two accounts, the same name of knowledge. So this is going to happen.

ThreadLocal and synchronization

Both the a.treadlocal and synchronization mechanisms are designed to resolve access conflicts for the same variables in multiple threads
B. the former adopts the method of "space for time", while the latter adopts the method of "time for space"

All clear now. Each has its own advantages and disadvantages, each has its own applicable scenarios, I hope this article can be helpful for you to have a deeper understanding of Java multithreading synchronization.


Related articles: