Summary and extension of thread synchronization

Posted by ferozsho on Sat, 15 Jan 2022 21:14:11 +0100

Thread synchronization mechanism

Thread synchronization refers to multiple threads operating on the same resource

Concurrency means that the same object is operated by multiple threads at the same time

in real life , We'll meet“ Same resource , Many people want to use it " Question of , such as , In the canteen, Everyone wants to eat, The most natural solution is Line up, One by one
When dealing with multithreading problems , Multiple threads access the same object , And some threads also want to modify this object At this time, we need thread synchronization. Thread synchronization is actually a waiting mechanism , Multiple threads that need to access this object at the same time enter this object Object's waiting pool Form a queue , Wait until the previous thread is used up , Next thread reuse

Because multiple threads of the same process share the same storage space , While bringing convenience , It also brings the problem of access conflict, In order to ensure the correctness of data when accessed in the method , Join on access Locking mechanism synchronized , When a thread obtains an exclusive lock on an object , Exclusive resources , Other threads must wait , release the lock after use. There are the following problems :
1. A thread holding a lock will cause all other threads that need the lock to hang ;
2. Under multithreading competition , Lock , Releasing the lock will lead to more context switching and scheduling delays , Cause performance problems;
3. If a high priority thread waits for a low priority thread to release the lock, the priority will be inverted, Cause performance problems.

Three unsafe cases

Case 1: ticket grabbing

//Unsafe ticket buying
public class UnsafeBuyTicket {
    public static void main(String args[]){
        BuyTicket station = new BuyTicket();
        new Thread(station,"student").start();
        new Thread(station,"teacher").start();
        new Thread(station,"cattle").start();
    }
}
class BuyTicket implements Runnable{
    //ticket
    private int ticketNums = 10;
    boolean flag = true;  //External stop mode
    public void run(){
        //Buy a ticket
        while(flag){
            try{
                buy();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    private void buy() throws InterruptedException{
        //Judge whether there are tickets
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //Analog delay
        Thread.sleep(100);
        //Buy a ticket
        System.out.println(Thread.currentThread().getName() + "Get" + ticketNums--);
    }
}

You can also get - 1 because:

Each thread interacts in its own working memory. Improper memory control will lead to inconsistent data

The popular explanation is that many people may grab one ticket at the same time, but there is only one ticket, so there will be 0 and - 1

Case 2: bank withdrawal

//Unsafe withdrawal
//Two people go to the bank to withdraw money and open an account
public class UnsafeBank {
    public static void main(String agrs[]){
        //account
        Account account = new Account(100,"Marriage fund");
        Drawing you = new Drawing(account,50,"you");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");
        you.start();
        girlFriend.start();
    }
}
//account
class Account{
    int money;  //balance
    String name;  //Card name
    public Account(int money,String name)
    {
        this.money = money;
        this.name = name;
    }
}
//Bank: simulated withdrawal
class Drawing extends Thread{
    Account account;  //account
    //How much did you withdraw
    int drawingMoney;
    //How much money do you have now
    int nowMoney;
    public Drawing(Account account,int drawingMoney,String name){
        super(name);  //It is equivalent to the parent class constructor (name). Drawing inherits Thread. One constructor in Thread class is String name
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //Withdraw money
    public void run(){
        //Judge whether there is money
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"There's not enough money to withdraw");
            return;
        }
        //Simulate a delay (not necessarily, here is just to find out the error)
        //sleep can amplify the occurrence of problems
        //Because the real code is definitely not used by you alone, but by millions of people
        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        //Card balance = balance - you get money
        account.money = account.money - drawingMoney;
        //The money in your hand
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name+"The balance is:"+account.money);
        //Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName()+"Money in hand:"+nowMoney);
    }
}

 

Case 3: thread

import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
    public static void main(String agrs[]){
        List<String> list = new ArrayList<String>();
        for (int i=0;i<10000;i++){
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

 

I can't get to 10000 because I have to go together...  

Synchronization method and synchronization block

Synchronization method

1. Because we can pass private Keyword to ensure that data objects can only be accessed by methods , So we just need to propose a mechanism for the method, This mechanism is the synchronized keyword , It includes two uses :
synchronized method And synchronized block
Synchronization method: public synchronized void method(int args) {}

2. The synchronized method controls access to the "object". Each object corresponds to a lock. Each synchronized method must obtain the lock of the object calling the method before execution, otherwise the thread will block. Once the method is executed, it will monopolize the lock until the method returns, and the lock will not be released until the blocked thread can obtain the lock and continue to execute

Defect: declaring a large method synchronized will affect efficiency

Disadvantages of synchronization method

The content that needs to be modified in the method needs to be locked. Too many locks waste resources

Practice:

Modification of three unsafe cases:

Case 1:

//Unsafe ticket buying
public class UnsafeBuyTicket {
    public static void main(String args[]){
        BuyTicket station = new BuyTicket();
        new Thread(station,"student").start();
        new Thread(station,"teacher").start();
        new Thread(station,"cattle").start();
    }
}
class BuyTicket implements Runnable {
    //ticket
    private int ticketNums = 10;
    boolean flag = true;  //External stop mode

    public void run() {
        //Buy a ticket
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //The synchronized synchronization method locks this
    private synchronized void buy() throws InterruptedException {
        //Judge whether there are tickets
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //Analog delay
        Thread.sleep(100);
        //Buy a ticket
        System.out.println(Thread.currentThread().getName() + "Get" + ticketNums--);
    }
}

This gives everyone else a chance to unlock the lock first, because it gives everyone else a big chance to unlock the lock first.

Modification:

//Unsafe ticket buying
public class UnsafeBuyTicket {
    public static void main(String args[]){
        BuyTicket station = new BuyTicket();
        new Thread(station,"student").start();
        new Thread(station,"teacher").start();
        new Thread(station,"cattle").start();
    }
}
class BuyTicket implements Runnable {
    //ticket
    private int ticketNums = 10;
    boolean flag = true;  //External stop mode

    public void run() {
        //Buy a ticket
        while (flag) {
            try {
                //Simulate the delay to avoid one person getting all the tickets. The sleep should be written here because the sleep will not unlock
                Thread.sleep(100);
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //The synchronized synchronization method locks this
    private synchronized void buy() throws InterruptedException {
        //Judge whether there are tickets
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //Buy a ticket
        System.out.println(Thread.currentThread().getName() + "Get" + ticketNums--);
    }
}

Because the buy method is locked, only one thread can call it at a time

Case 2:

If the method of case 1 is also used for modification:

//Unsafe withdrawal
//Two people go to the bank to withdraw money and open an account
public class UnsafeBank {
    public static void main(String agrs[]){
        //account
        Account account = new Account(100,"Marriage fund");
        Drawing you = new Drawing(account,50,"you");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");
        you.start();
        girlFriend.start();
    }
}
//account
class Account{
    int money;  //balance
    String name;  //Card name
    public Account(int money,String name)
    {
        this.money = money;
        this.name = name;
    }
}
//Bank: simulated withdrawal
class Drawing extends Thread{
    Account account;  //account
    //How much did you withdraw
    int drawingMoney;
    //How much money do you have now
    int nowMoney;
    public Drawing(Account account,int drawingMoney,String name){
        super(name);  //It is equivalent to the parent class constructor (name). Drawing inherits Thread. One constructor in Thread class is String name
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //Withdraw money
    public synchronized void run(){  //Modification method of case 1
        //Judge whether there is money
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"There's not enough money to withdraw");
            return;
        }
        //Simulate a delay (not necessarily, here is just to find out the error)
        //sleep can amplify the occurrence of problems
        //Because the real code is definitely not used by you alone, but by millions of people
        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        //Card balance = balance - you get money
        account.money = account.money - drawingMoney;
        //The money in your hand
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name+"The balance is:"+account.money);
        //Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName()+"Money in hand:"+nowMoney);
    }
}

The result is still wrong:

This is because the default lock of synchronized is this, and the bank to be locked here.

That is, locking the bank instead of locking this refers to the current class, that is, the class drawing of the bank class
The synchronized method wants the lock object to be the class of the current class. Either there can only be one instance object of the bank, or the synchronized method is decorated with static. This can also ensure that the lock object is the class of the class
And he has two bank instance objects, you and girlfriend. Then these two instance objects open two threads. The lock object used by each thread is the current instance object. If the lock object is different, synchronization cannot be realized.
The simplest way is to use the synchronized code block to put the class of the current class in the synchronized() {} and parentheses. Because a class can only have one class, it can ensure that the lock objects are the same and realize synchronization
ps: as long as there is a unique object class in the parentheses, you can actually remember the following three points, and you won't make any mistake about the synchronization method or the lock object in the synchronization code block at any time
For normal synchronization methods, the lock is the current instance object. If there are multiple instances, the lock objects must be different and synchronization cannot be achieved.
For static synchronization methods, the lock is the Class object of the current Class. There are multiple instances, but the lock object is the same, and synchronization can be completed.
For synchronized method blocks, locks are objects configured in synchronized parentheses. It is better to have only one object. For example, if the class of the current class has only one lock object, synchronization can also be realized.  

Correct writing:

Synchronization block

1. Synchronization block: synchronized (Obj) {}

2. Obj calls it synchronization monitor

(1) Obj can be any object, but it is recommended to use shared resources as synchronization monitors

(2) There is no need to specify a synchronization monitor in the synchronization method, because the synchronization monitor of the synchronization method is this

The object itself , Or class [ Explanation in reflection ]
3. Execution process of synchronization monitor
(1) First thread access , Lock synchronization monitor , Execute the code .
(2) Second thread access , Synchronization monitor found locked , cannot access .
(3) First thread access completed , Unlock sync monitor .
(4) Second thread access , The synchronization monitor was found to have no lock , Then lock and access
After modification:
//Unsafe withdrawal
//Two people go to the bank to withdraw money and open an account
public class UnsafeBank {
    public static void main(String agrs[]){
        //account
        Account account = new Account(100,"Marriage fund");
        Drawing you = new Drawing(account,50,"you");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");
        you.start();
        girlFriend.start();
    }
}
//account
class Account{
    int money;  //balance
    String name;  //Card name
    public Account(int money,String name)
    {
        this.money = money;
        this.name = name;
    }
}
//Bank: simulated withdrawal
class Drawing extends Thread{
    Account account;  //account
    //How much did you withdraw
    int drawingMoney;
    //How much money do you have now
    int nowMoney;
    public Drawing(Account account,int drawingMoney,String name){
        super(name);  //It is equivalent to the parent class constructor (name). Drawing inherits Thread. One constructor in Thread class is String name
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //Withdraw money
    public void run(){
       //The object of the lock is the amount of change, which needs to be added, deleted and modified
       synchronized (account){  //After modification
           //Judge whether there is money
           if (account.money-drawingMoney<0){
               System.out.println(Thread.currentThread().getName()+"There's not enough money to withdraw");
               return;
           }
           //Simulate a delay (not necessarily, here is just to find out the error)
           //sleep can amplify the occurrence of problems
           //Because the real code is definitely not used by you alone, but by millions of people
           try{
               Thread.sleep(1000);
           }catch(InterruptedException e){
               e.printStackTrace();
           }
           //Card balance = balance - you get money
           account.money = account.money - drawingMoney;
           //The money in your hand
           nowMoney = nowMoney + drawingMoney;
           System.out.println(account.name+"The balance is:"+account.money);
           //Thread.currentThread().getName() = this.getName()
           System.out.println(this.getName()+"Money in hand:"+nowMoney);
       }
    }
}

Case 3:

import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
    public static void main(String agrs[]){
        List<String> list = new ArrayList<String>();
        for (int i=0;i<10000;i++){
            new Thread(()->{
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

Topics: Java Singleton pattern