Thread part 3 - synchronization and locking

Thread synchronization mechanism

Thread synchronization: multiple threads operate on the same resource
In fact, it is a waiting mechanism. Multiple threads that need to access this object at the same time enter the waiting pool of the object to form a queue

Concurrency: the same object is operated by multiple threads at the same time
For example, grab tickets and withdraw money from the bank

In order to ensure the correctness of data access in the method, the lock mechanism synchronized is added during access. When one thread obtains the exclusive lock of the object and monopolizes resources, other threads must wait and release the lock after use

Using locks can cause the following problems:

  • Holding a lock by one thread will cause all other threads that need the lock to hang
  • In multi-threaded competition, locking and releasing locks will lead to more context switching and scheduling delays, resulting in performance problems
  • If a high priority thread waits for a low priority thread to release the lock, it will lead to priority inversion and performance problems

Three unsafe cases

//Ticket grabbing

package Thread.syn;
//Unsafe ticket buying
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"yyqx").start();
        new Thread(buyTicket,"wyb").start();
        new Thread(buyTicket,"ltt").start();

    }
}

class BuyTicket implements Runnable{

    private int ticketNums = 10;
    boolean flag = true;   //External stop mode
    @Override
    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(1000);
        System.out.println(Thread.currentThread().getName()+"Bought the second"+ticketNums--+"Ticket");
    }
}

//Bank withdrawal

package Thread.syn;
//Unsafe withdrawal
public class UnsafeBank {
    public static void main(String[] args) {
        //account
        Account account = new Account(100,"ddd");

        Drawing you = new Drawing(account,50,"lllll");
        Drawing yourFriend = new Drawing(account,100,"ttttt");

        you.start();
        yourFriend.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
    int drawingMoney;//How much did you withdraw
    int nowMoney;//How much money do you have now

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //Withdraw money
    @Override
    public void run(){
        //Judge whether there is money
        if(account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"Insufficient balance, unable to withdraw money");
            return;
        }
        //sleep can amplify the occurrence of problems
        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);
        System.out.println(this.getName()+"Money in hand:"+nowMoney);
    }

}

//Thread unsafe

package Thread.syn;

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

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

Synchronization method and synchronization block

Synchronization method: synchronized method

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 it can be executed. Otherwise, the thread will block. Once the method is executed, it will monopolize the lock until the method returns. The blocked thread can obtain the lock and continue to execute
Defect: declaring a large method synchronized will affect efficiency

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

Synchronized blocks: synchronized blocks

synchronized(obj){ }
obj calls it a synchronization monitor
obj = the object of the lock is the amount of change and needs to be added, deleted or modified

Testing a collection of JUC security types

package Thread.syn;

import java.util.concurrent.CopyOnWriteArrayList;

//Testing a collection of JUC security types
public class TestJUC {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(3000);
    }
}

deadlock

A "deadlock" may occur when a synchronization block has "locks of more than two objects" at the same time

Necessary conditions for deadlock generation:

  1. Mutex condition: a resource can only be used by one process at a time.
  2. Request and hold condition: when a process is blocked by requesting resources, it will hold on to the obtained resources.
  3. Conditions of non deprivation: the resources obtained by the process cannot be forcibly deprived before they are used up
  4. Circular waiting condition: a circular waiting resource relationship is formed between several processes
package Thread;
//Deadlock: multiple threads hold each other's required resources, and then form a deadlock
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0,"li");
        Makeup g2 = new Makeup(1,"tang");

        g1.start();
        g2.start();
    }
}

//Lipstick
class Lipstick{

}
//mirror
class Mirror{

}

class Makeup extends Thread{
    //Only one resource is needed, which is guaranteed by static
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//choice
    String girlName;//People who use cosmetics

    Makeup(int choice,String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run(){
        //Make up
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //Makeup, holding each other's locks, is to get each other's resources
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick) {//Get lipstick lock
            System.out.println(this.girlName+"Get lipstick lock");
            Thread.sleep(1000);
            }
            synchronized (mirror){//Get the mirror in a second
                System.out.println(this.girlName+"Get the lock of the mirror");
            }
        }else{
            synchronized (mirror) {//Get lipstick lock
                System.out.println(this.girlName+"Get the lock of the mirror");
                Thread.sleep(2000);
            }
            synchronized (lipstick){//Get the mirror in a second
                System.out.println(this.girlName+"Get lipstick lock");
            }
        }
    }
}

Lock lock

juc=java.util.concurrent
ReentrantLock - reentrant lock with the same concurrency and memory semantics as synchronized

class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
//Thread safe code;
}
finally{
lock.unlock();
//If there is an exception in the synchronization code, write unlock() to the finally statement block
}
}
}

Comparison of synchronized and Lock

  • Lock is an explicit lock (manual opening and closing lock). synchronized is an implicit lock, which is automatically released out of the scope
  • Lock only has code block lock, and synchronized has code block lock and method lock
  • Using Lock lock, the JVM takes less time, has better performance and better scalability
  • Priority: Lock > synchronize code block (it has entered the method body and allocated corresponding resources) > synchronize method (outside the method body)
package Thread;

import java.util.concurrent.locks.ReentrantLock;

//Test Lock
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();

    }
}

class TestLock2 implements Runnable {

    int ticketNums = 10;
    //Define lock lock
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();//Lock
            try {
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            }finally{
                //Unlock
                lock.unlock();
            }

        }
    }
}

Keywords: Java

Added by tgh on Sat, 20 Nov 2021 16:00:54 +0200