Thread synchronization mechanism for Java multithreading (locks, thread pools, etc.)

1. Concept

1. Concurrency

The same object is operated by multiple threads at the same time

2, cause

When dealing with multi-threading problems, multiple threads access the same object, and some threads want to modify this object, thread synchronization is required, thread synchronization is actually a waiting mechanism, multiple threads that need to access this object at the same time enter this object The waiting pool forms a queue, waiting for the previous thread to be used up, and then the next thread will use it. However, since multiple threads of the same process share the same storage space, while bringing convenience, it also brings access conflicts. In order to ensure the correctness of data when accessed in the method, on this basis, add locks mechanism.

3. Disadvantages

Holding a lock by one thread causes all other threads that need the lock to hang.
(1) Under multi-thread competition, adding and releasing locks will cause more context switching and scheduling delays, causing performance problems.
(2) If a high-priority thread waits for a low-priority thread to release the lock, it will cause priority inversion and cause performance problems.

Two, three major unsafe cases

1. Example 1 (simulation of ticket purchase scene)

When multiple threads are concurrent, if the queue is not designed well, the result will be unsafe, and -1 will appear

package com.example.multithreading.demo12_syn;

// 线程不安全,有负数
public class UnsafeBuyTicket {
    
    
    public static void main(String[] args) {
    
    
        BuyTicket station = new BuyTicket();

        new Thread(station, "自己").start();
        new Thread(station, "其他人").start();
        new Thread(station, "黄牛").start();
    }

}

class BuyTicket implements Runnable{
    
    

    // 票
    private int ticketNums = 10;
    // 外部停止方式
    boolean flag = true;

    @Override
    public void run() {
    
    
        // 买票
        while(flag) {
    
    
            try {
    
    
                buy();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
    
    
        // 判断是否有票
        if (ticketNums <= 0){
    
    
            flag = false;
            return;
        }
        // 模拟延时
        Thread.sleep(100);

        // 买票
        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
    }
}

result
insert image description here

2. Example 2 (simulating money withdrawal scene)

package com.example.multithreading.demo12_syn;

import lombok.Data;

public class UnsafeBank {
    
    
    public static void main(String[] args) {
    
    
        // 账户
        Account account = new Account(100,"基金");

        Drawing boy = new Drawing(account,50,"自己");
        Drawing girlFriend = new Drawing(account,100,"女朋友");

        boy.start();
        girlFriend.start();
    }
}

// 账户
@Data
class Account {
    
    
    // 余额
    int money;
    // 卡名
    String name;

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

// 模拟取钱
@Data
class Drawing extends Thread{
    
    
    // 账户
    Account account;
    // 取钱数
    int drawingMoney;
    // 持有钱数
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
    
    
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
        this.nowMoney = nowMoney;
    }

    @Override
    public void run() {
    
    
        // 判断是否有钱
        if (account.money - drawingMoney < 0){
    
    
            System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
            return;
        }

        try{
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        // 卡内余额 = 余额 - 取的钱
        account.money = account.money - drawingMoney;
        // 你手里的钱
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "余额为:" + account.money);
        System.out.println(this.getName() + "手里的钱:" + nowMoney);
    }
}


result
insert image description here

3. Example 3 (simulation collection)

package com.example.multithreading.demo12_syn;

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

// 线程不安全的集合
public class UnsafeList {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(()->{
    
    
                list.add(Thread.currentThread().getName());
            }).start();
        }

        System.out.println(list.size());
    }
}

result (really should be 1000)
insert image description here

3. Synchronization method and synchronization block

1. Synchronization method

Since the private keyword can be used to ensure that the data object can only be accessed by the method, it is only necessary to propose a mechanism for the method, which is the synchronized keyword. Two usages (synchronized method and synchronized block)

public synchronized void method(int args){
    
    }

The synchronized method controls access to the "object". Each object corresponds to a lock. Each synchronized method must obtain the lock of the object that calls the method before it can be executed. Otherwise, the thread will block. Once the method is executed, it will exclusively occupy the lock until The lock is released only after the method returns, and the blocked thread can obtain the lock and continue execution. (Disadvantage: If a large method is declared as synchronized, it will affect efficiency)
insert image description here

2. Synchronization block

(1) format

synchronized(obj){
    
    }

(2) Obj is called a synchronization monitor.
Obj can be any object, but it is recommended to use shared resources as a synchronization monitor.
There is no need to specify a synchronization monitor in the synchronization method, because the synchronization monitor of the synchronization method is this, which is the object itself.
(3) The execution process of the synchronization monitor
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 accesses, the synchronization monitor is unlocked.
The second thread accesses, finds that the synchronization monitor does not have a lock, then locks and accesses.
insert image description here

4. Collection of JUC security types

1. Thread-safe collection (combined with delay implementation)

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

2. Sample

package com.example.multithreading.demo12_syn;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

// 线程不安全的集合
public class UnsafeList {
    
    
    public static void main(String[] args) {
    
    
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(()->{
    
    
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try{
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        System.out.println(list.size());
    }
}

result
insert image description here

Five, deadlock

1. Concept

Multi-threads each occupy some shared resources and wait for each other to run on the resources occupied by other threads, resulting in a situation where two or more threads are waiting for each other to release resources and stop executing. When a synchronization block holds "locks of more than two objects" at the same time, the problem of "deadlock" may occur.

2. Deadlock example

package com.example.multithreading.demo13_DeadLock;

// 死锁:多个线程互相占有需要的资源,然后形成僵持
public class DeadLock {
    
    

    public static void main(String[] args) {
    
    
        Makeup g1 = new Makeup(0,"张三");
        Makeup g2 = new Makeup(1,"李四");

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

class Lipstick {
    
    

}

class Mirror {
    
    

}

class Makeup extends Thread {
    
    
    // 需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    // 选择
    int choice;
    // 女孩
    String girlName;

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

    @Override
    public void run() {
    
    
        try {
    
    
            makeup();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
    }

    private void makeup() throws InterruptedException {
    
    
        if (choice == 0){
    
    
            synchronized (lipstick){
    
    
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);

                synchronized (mirror){
    
    
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }
        } else{
    
    
            synchronized (mirror){
    
    
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);

                synchronized (lipstick){
    
    
                    System.out.println(this.girlName + "获得看口红的锁");
                }
            }
        }
    }
}

Result (will get stuck here)
insert image description here

3. Solutions

package com.example.multithreading.demo13_DeadLock;

// 死锁:多个线程互相占有需要的资源,然后形成僵持
public class DeadLock {
    
    

    public static void main(String[] args) {
    
    
        Makeup g1 = new Makeup(0,"张三");
        Makeup g2 = new Makeup(1,"李四");

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

class Lipstick {
    
    

}

class Mirror {
    
    

}

class Makeup extends Thread {
    
    
    // 需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    // 选择
    int choice;
    // 女孩
    String girlName;

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

    @Override
    public void run() {
    
    
        try {
    
    
            makeup();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
    }

    private void makeup() throws InterruptedException {
    
    
        if (choice == 0){
    
    
            synchronized (lipstick){
    
    
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror){
    
    
                System.out.println(this.girlName + "获得镜子的锁");
            }
        } else{
    
    
            synchronized (mirror){
    
    
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick){
    
    
                System.out.println(this.girlName + "获得看口红的锁");
            }
        }
    }
}


result
insert image description here

4. Four necessary conditions for deadlock

(1) Mutually exclusive conditions: a resource can only be used by one process at a time.
(2) Request and hold conditions: When a process is blocked due to requesting resources, it will hold on to the obtained resources.
(3) Non-deprivation conditions: The resources obtained by the process cannot be forcibly deprived before they are used up.
(4) Circular waiting condition: A head-to-tail cyclic waiting resource relationship is formed among several processes.
Deadlock can be avoided by breaking any one or more of these conditions.

Six, Lock (lock)

1. Concept

Starting from JDK5.0, Java provides a more powerful thread synchronization mechanism - synchronization is achieved by explicitly defining a synchronization lock object, and the synchronization lock uses a Lock object to act.
The java.util.concurrent.locks.Lock interface is a tool for controlling access to shared resources by multiple threads.
The lock provides exclusive access to shared resources. Only one thread can lock the Lock object at a time, and the thread should obtain the Lock object before it starts to access the shared resource.
The ReentrantLock class implements Lock, which has the same concurrency and memory semantics as synchronized. In implementing thread-safe control, ReentrantLock is more commonly used, which can explicitly lock and release locks.

2. The comparison between synchronized and Lock

(1) Lock is an explicit lock (open and close the lock manually), and synchronized is an implicit lock, which is automatically released when it goes out of scope.
(2) Lock only has code block locks, and synchronized has code block locks and method locks.
(3) Using the Lock lock, the JVM will spend less time scheduling threads and have better performance. And it has better scalability (provide more subclasses)
priority
lock > synchronization code block (has entered the method body and allocated corresponding resources) > synchronization method (outside the method body)

3. Sample

package com.example.multithreading.demo14_Lock;

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    
    
    public static void main(String[] args) {
    
    
        TicketLock threadTest1 = new TicketLock();

        new Thread(threadTest1).start();
        new Thread(threadTest1).start();
        new Thread(threadTest1).start();
    }
}

class TicketLock implements Runnable{
    
    

    int ticketNums = 10;

    // 定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
    
    
        while (true) {
    
    
            try{
    
    
                // 加锁
                lock.lock();
                if(ticketNums > 0){
    
    
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                    System.out.println(ticketNums--);
                }else {
    
    
                    break;
                }
            }finally {
    
    
                // 解锁
                lock.unlock();
            }
        }
    }
}

result
insert image description here

Seven, thread cooperation

1. Tube method

Producer: the module responsible for producing data (may be a method, object, thread, process)
consumer: the module responsible for processing data (may be method, object, thread, process)
buffer: the consumer cannot directly use the producer's data , there is a buffer between the two.
The producer puts the produced data into the buffer, and the consumer takes the data out of the buffer.

package com.example.multithreading.demo15_PC;

import java.beans.Customizer;

// 管程法
// 生产者,消费者,产品,缓冲区
public class PcTest {
    
    
    public static void main(String[] args) {
    
    
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

// 生产者
class Productor extends Thread{
    
    
    SynContainer container;

    public Productor(SynContainer container){
    
    
        this.container = container;
    }

    // 生产
    @Override
    public void run(){
    
    
        for(int i = 0; i < 100; i++){
    
    
            container.push(new Chicken(i));
            System.out.println("生产了->第" + i + "只鸡");
        }
    }
}

// 消费者
class Consumer extends Thread{
    
    
     SynContainer container;

     public Consumer(SynContainer container){
    
    
         this.container = container;
     }

    // 消费
    @Override
    public void run(){
    
    
         for(int i = 0; i < 100; i++){
    
    
             System.out.println("消费了---->第" + container.pop().id + "只鸡");
         }
    }
}

// 产品
class Chicken{
    
    
    // 产品编号
    int id;

    public Chicken(int id) {
    
    
        this.id = id;
    }
}

// 缓冲区
class SynContainer {
    
    

    // 需要一个容器大小
    Chicken[] chickens = new Chicken[10];

    // 容器计数器
    int count = 0;

    // 生产者放入产品
    public synchronized void push(Chicken chicken){
    
    
        // 如果容器满了,就需要等待消费者消费
        if (count == chickens.length){
    
    
            // 通知消费者消费,生产等待
            try {
    
    
                this.wait();
            }catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        // 如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count++;

        // 可以通知消费者消费了
        this.notifyAll();
    }

    // 消费者消费产品
    public synchronized Chicken pop(){
    
    
        // 判断能否消费
        if (count == 0){
    
    
            // 等待生产者生产,消费者等待
            try{
    
    
                this.wait();
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }

        // 如果可以消费
        count--;
        Chicken chicken = chickens[count];

        // 吃完了,通知生产者生产
        this.notifyAll();

        return chicken;
    }


}

result
insert image description here

2. Signal light method

Controlled by flag bits

package com.example.multithreading.demo15_PC;

// 信号灯法,标志位解决
public class PcTest2 {
    
    
    public static void main(String[] args) {
    
    
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// 生产者 --> 演员
class Player extends Thread{
    
    
    TV tv;
    public Player(TV tv) {
    
    
        this.tv = tv;
    }

    @Override
    public void run() {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            if(i%2==0){
    
    
                this.tv.play("电视剧");
            } else{
    
    
                this.tv.play("电影");
            }
        }
    }
}

// 消费者 --> 观众
class Watcher extends Thread{
    
    
    TV tv;
    public Watcher(TV tv) {
    
    
        this.tv = tv;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            tv.watch();
        }
    }
}

// 产品 --> 节目
class TV{
    
    
    // 演员表演,观众等待 T
    // 观众观看,演员等待 F
    // 表演的节目
    String voice;
    // 表演的节目
    boolean flag = true;

    // 表演
    public synchronized void play(String voice){
    
    
        if (!flag){
    
    
            try{
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        System.out.println("演员表演了:" + voice);
        // 通知观众观看
        // 通知唤醒
        this.notifyAll();
        this.voice = voice;
        this.flag = false;
    }

    // 观看
    public synchronized void watch(){
    
    
        if(flag){
    
    
            try{
    
    
                this.wait();
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + voice);
        // 通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

result
insert image description here

3. Thread pool

3.1 Background

Resources that are frequently created and destroyed and use a large amount of resources, such as threads in concurrent situations, have a great impact on performance.

3.2 Ideas

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

3.3 Benefits

(1) Improve response speed (reduce the time to create new threads)
(2) Reduce resource consumption (reuse threads in the thread pool, do not need to create each time)
(3) Facilitate thread management
corePoolSize: the size of the core pool
maximumPoolSize: Maximum number of threads
keepAliveTime: How long does a thread keep when there is no task at most before it will be terminated

3.4 ExecutorService和Executors

ExecutorService: The real thread pool interface (common subclass ThreadPoolExecutor)
void execute(Runnable command): Execute tasks/commands, without return value, generally used to execute Runnable.
Future submit(Callable task): Executes the task, has a return value, and is generally used to execute Callable.
void shutdown() : Close the connection pool.
Executors: Tool classes, factory classes for thread pools, used to create and return different types of thread pools.

3.5 Sample

package com.example.multithreading.demo16_Pool;

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

// 测试线程池
public class PoolTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1、创建服务,创建线程池
        // newFixedThreadPool 参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 2、关闭链接
        service.shutdown();
    }
}

class MyThread implements Runnable {
    
    

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

result
insert image description here

Guess you like

Origin blog.csdn.net/qq_46106857/article/details/128204183