Thread synchronization and thread pool of Java multithreading

catalogue

Thread synchronization

Synchronization method

Synchronization block

deadlock

Lock lock

Use form

synchronize vs Lock

Thread pool

Thread synchronization

  • Multiple threads operate on the same resource

  • Concurrency: the same object is operated by multiple threads at the same time

  • Thread synchronization is actually a waiting mechanism. Multiple threads that need to access this object at the same time enter the waiting pool of this object to form a queue, wait for the previous thread to use it, and then use it for the next thread

  • Forming condition: queue + lock -- > solves thread safety problems

    • For example, queue up to go to the bathroom, lock the door after one goes in, recognize that the lock is open, and the next can go in again

    • Performance inversion is easy to occur after locking, that is, threads with low priority get the lock first, resulting in threads with high priority unable to execute

    • Only the contents that need to be modified need to be locked. Too many locks will cause a waste of resources

Synchronization method

         synchronized methods control access to "objects". Each object corresponds to a lock. Each synchronize d 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. The blocked thread can obtain the lock and continue to execute.

//The ticket grabbing method is not synchronized with the synchronize keyword, and the thread is unsafe. The number of tickets sold is greater than the actual number, that is, the remaining tickets are negative or different people grab the same ticket
public class StationBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"Zhang San").start();
        new Thread(buyTicket,"Li Si").start();
        new Thread(buyTicket,"Wang Wu").start();
    }
}

class BuyTicket implements Runnable{
    private int ticketNums = 10;//Number of votes
    private boolean flag = true;//External thread stop mode flag bit

    @Override
    public void run() {
        while(flag){
            buy();
        }
    }

    //In the synchronized synchronization method, the default lock is this itself, that is, BuyTicket
    private synchronized void buy(){
        if (ticketNums <= 0){//The tickets were robbed
            flag = false;
            return;
        }else {
            try {
                Thread.sleep(100);//sleep simulates the delay, which can amplify the occurrence of the problem
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "Got the third" + ticketNums-- + "Ticket");
        }
    }
}

Synchronization block

  • Synchronization block: synchronize(Obj) {}

  • Obj is called synchronization monitor

    • It can be any object. It is recommended to use shared resources

    • There is no need to specify a synchronization monitor in the synchronization method, because the synchronization monitor of the synchronization method is this or class

  • Synchronization monitor execution process

    • The first thread accesses, locks the synchronization monitor, and executes the code in it

    • The second thread accesses and finds that the synchronization monitor is locked and cannot be accessed

    • After the first thread is executed, unlock the synchronization monitor

    • The second thread accesses, finds that the synchronization monitor has no lock, and then locks and accesses

public class BankDrawMoney {
    public static void main(String[] args) {
        //Create bank account
        Account account = new Account("Marriage fund",1000);

        //Withdrawal object
        WithdrawMoney you = new WithdrawMoney(account,50,"you");
        WithdrawMoney girlFriend = new WithdrawMoney(account,100,"girl friend");

        you.start();
        girlFriend.start();

    }
}

//account
class Account{
    private String name;//Account name
    private int money;//balance

    public Account(String name, int money){
        this.money = money;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}

//Bank: simulated withdrawal
class WithdrawMoney extends Thread{
    Account account;//account
    int drawingMoney;//How much did you withdraw
    int nowMoney;//How much money do you have now

    public WithdrawMoney(Account account, int drawingMoney, String name){
        super(name);//Call the parameterized constructor of the parent class
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //Withdraw money
    @Override
    public void run() {
        //The amount of lock is the amount of change, that is, the amount that needs to be added, deleted and modified
        synchronized (account){
            //First, judge whether there is money in the account
            if(account.getMoney() - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName() + "Insufficient balance, unable to withdraw!");
                return;
            }

            try {
                Thread.sleep(1000);//sleep amplification problem
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Card balance
            account.setMoney(account.getMoney() - drawingMoney);

            //Money in hand
            nowMoney = nowMoney + drawingMoney;

            System.out.println(Thread.currentThread().getName() + "Money in hand:" + nowMoney);
            System.out.println(account.getName() + "The account balance is:" + account.getMoney());
        }
    }
}

deadlock

         Multiple threads occupy some shared resources and wait for the resources occupied by other threads to continue execution. As a result, two or more threads are waiting for each other to release resources and stop execution.

         When a synchronization block has "locks of more than two objects" at the same time, the problem of "deadlock" may occur.

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. Cyclic waiting condition: a head to tail cyclic waiting resource relationship is formed between several processes.

         As long as one or more of the above four conditions are solved, the deadlock problem can be avoided

//Deadlock: multiple threads hold each other's required resources, forming a deadlock
public class DeadLock {
    public static void main(String[] args) {
        Makeup girl1 = new Makeup(0,"Marry");
        Makeup girl2 = new Makeup(1,"Lily");

        girl1.start();
        girl2.start();
        //At this time, Lily obtains the lipstick lock and Mary obtains the mirror lock. Both of them occupy the shared resources, resulting in a deadlock and the program cannot continue to execute
        //Solution: after everyone gets the resource, release it first, and then wait for the second resource, that is, it is not written in the way of nested synchronization blocks
    }
}

//Lipstick
class Lipstick{
}
//mirror
class Mirror{
}

//Make up
class Makeup extends Thread{
    //Existing resources: a mirror, a lipstick
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//Selection
    String girlName;//Makeup man

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

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //Make up and hold each other's locks
    private void makeup() throws InterruptedException {
        if (choice == 0){//Take the mirror first
            synchronized (mirror){//Get mirror lock
                System.out.println(this.girlName + "Got the mirror lock");
                Thread.sleep(1000);
                synchronized (lipstick){//Lock the mirror and wait for the lipstick lock
                    System.out.println(this.girlName + "Obtained lipstick lock");
                }
            }
        }else if (choice == 1){//Get lipstick first
            synchronized (lipstick){//Get lipstick lock
                System.out.println(this.girlName + "Obtained lipstick lock");
                Thread.sleep(2000);
                synchronized (mirror){//Hold the lipstick lock and wait for the mirror lock
                    System.out.println(this.girlName + "Got the mirror lock");
                }
            }
        }
    }
}

Lock lock

  • Thread synchronization mechanism -- synchronization is achieved by displaying and defining the synchronization Lock object, which uses the Lock object as the synchronization Lock.

  • The java.util.concurrent.locks.Lock interface is a tool that controls multiple threads to access shared resources. Threads should obtain the Lock object before accessing shared resources.

  • ReentrantLock class implements Lock and has the same concurrency and memory semantics as synchronize. ReentrantLock is commonly used to display locking and releasing locks.

Use form

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

//Thread safety control of ticket buying is realized through Lock lock
public class Lock {
    public static void main(String[] args) {
        TestReentrantLock reentrantLock = new TestReentrantLock();
        new Thread(reentrantLock).start();
        new Thread(reentrantLock).start();
        new Thread(reentrantLock).start();
    }
}

class TestReentrantLock implements Runnable{
    int ticketNums = 10;
    //Define lock lock
    private final ReentrantLock lock = new ReentrantLock();

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

synchronize vs Lock

  • Lock is an explicit lock (which can be opened and closed manually. Don't forget to close the lock), and synchronize is an implicit lock, which is automatically released out of the scope.

  • Lock only has code block lock, and synchronize has code block lock and method lock.

  • Using Lock lock JVM will take less time to schedule threads and perform better. And it has better scalability (providing more subclasses).

  • Priority:

    • Lock > synchronize code block (it has entered the method body and allocated corresponding resources) > synchronize method (outside the method body)

Thread pool

Create multiple threads in advance, put them into the thread pool, obtain them directly when using them, and put them back into the thread pool after using them. It can avoid frequent creation and destruction and realize reuse.

Benefits:

  • Increase the corresponding speed (reduce the time for new threads)

  • Reduce resource consumption (you don't need to create threads every time)

  • Easy thread management

    • corePoolSize: the size of the core pool

    • maximumPoolSize: maximum number of threads

    • keepAliveTime: when the thread has no task, how long will it be terminated after holding it for at most

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPool {
    public static void main(String[] args) {
        //Create service, create thread pool
        //Real thread pool interface
        //Executors tool class and thread pool factory class are used to create and return different types of thread pools
        ExecutorService service = Executors.newFixedThreadPool(10);//The parameter is the thread pool size

        //Execute executes tasks / commands. It is generally used to execute Runnable
        //submit has a return value. The return value type is future < T >, which is generally used to execute Callable
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //Close connection pool
        service.shutdown();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

Keywords: Java Multithreading thread pool

Added by wiseoleweazel on Wed, 29 Sep 2021 00:16:07 +0300