Mécanisme de synchronisation des threads pour le multithreading Java (verrous, pools de threads, etc.)

1. Concept

1. Concurrence

Le même objet est géré par plusieurs threads en même temps

2, parce que

Lorsque vous traitez des problèmes multithread, plusieurs threads accèdent au même objet et certains threads souhaitent modifier cet objet, la synchronisation des threads est requise. La synchronisation des threads est en fait un mécanisme d'attente. Plusieurs threads qui doivent accéder à cet objet en même temps entrent cet objet Le pool d'attente forme une file d'attente, attendant que le thread précédent soit épuisé, puis le thread suivant l'utilisera. Cependant, étant donné que plusieurs threads du même processus partagent le même espace de stockage, tout en apportant de la commodité, cela entraîne également des conflits d'accès. Afin de garantir l'exactitude des données lors de l'accès dans la méthode, sur cette base, ajoutez un mécanisme de verrouillage.

3. Inconvénients

Le fait de maintenir un verrou par un thread entraîne le blocage de tous les autres threads qui ont besoin du verrou.
(1) Dans un contexte de concurrence multithread, l'ajout et la libération de verrous entraîneront davantage de changements de contexte et de retards de planification, entraînant des problèmes de performances.
(2) Si un thread de priorité élevée attend qu'un thread de faible priorité libère le verrou, cela entraînera une inversion de priorité et des problèmes de performances.

Deux, trois cas dangereux majeurs

1. Exemple 1 (simulation d'une scène d'achat de billets)

Lorsque plusieurs threads sont simultanés, si la file d'attente n'est pas bien conçue, le résultat sera dangereux et -1 apparaîtra

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--);
    }
}

résultat
insérez la description de l'image ici

2. Exemple 2 (simulation de scène de retrait d'argent)

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);
    }
}


résultat
insérez la description de l'image ici

3. Exemple 3 (collection de simulation)

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());
    }
}

résultat (devrait vraiment être 1000)
insérez la description de l'image ici

3. Méthode de synchronisation et bloc de synchronisation

1. Méthode de synchronisation

Puisque le mot-clé private peut être utilisé pour garantir que l'objet de données n'est accessible que par la méthode, il suffit de proposer un mécanisme pour la méthode, qui est le mot-clé synchronisé. Deux usages (méthode synchronisée et bloc synchronisé)

public synchronized void method(int args){
    
    }

La méthode synchronisée contrôle l'accès à "l'objet". Chaque objet correspond à un verrou. Chaque méthode synchronisée doit obtenir le verrou de l'objet qui appelle la méthode avant de pouvoir être exécutée. Sinon, le thread se bloquera. Une fois la méthode exécutée , il occupera exclusivement le verrou jusqu'à ce que le verrou soit libéré seulement après le retour de la méthode, et le thread bloqué pourra obtenir le verrou et continuer l'exécution. (Inconvénient : si une méthode volumineuse est déclarée synchronisée, cela affectera l'efficacité)
insérez la description de l'image ici

2. Bloc de synchronisation

(1) format

synchronized(obj){
    
    }

(2) Obj est appelé moniteur de synchronisation.
Obj peut être n'importe quel objet, mais il est recommandé d'utiliser des ressources partagées comme moniteur de synchronisation. Il
n'est pas nécessaire de spécifier un moniteur de synchronisation dans la méthode de synchronisation, car le moniteur de synchronisation de la synchronisation La méthode est la suivante, qui est l'objet lui-même.
(3) Le processus d'exécution du moniteur de synchronisation
Le premier thread accède, verrouille le moniteur de synchronisation et exécute le code qu'il contient.
Le deuxième thread accède et constate que le moniteur de synchronisation est verrouillé et inaccessible.
Après le premier accès au thread, le moniteur de synchronisation est déverrouillé.
Le deuxième thread accède, constate que le moniteur de synchronisation n'a pas de verrou, puis se verrouille et accède.
insérez la description de l'image ici

4. Collection de types de sécurité JUC

1. Collection thread-safe (combinée à une implémentation différée)

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

2. Échantillon

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());
    }
}

résultat
insérez la description de l'image ici

Cinq, impasse

1. Concept

Les multithreads occupent chacun des ressources partagées et attendent les uns les autres pour s'exécuter sur les ressources occupées par d'autres threads, ce qui entraîne une situation dans laquelle deux ou plusieurs threads s'attendent pour libérer des ressources et cesser de s'exécuter. Lorsqu'un bloc de synchronisation détient des « verrous de plus de deux objets » en même temps, le problème de « blocage » peut survenir.

2. Exemple de blocage

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 + "获得看口红的锁");
                }
            }
        }
    }
}

Résultat (restera bloqué ici)
insérez la description de l'image ici

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 + "获得看口红的锁");
            }
        }
    }
}


résultat
insérez la description de l'image ici

4. Quatre conditions nécessaires à une impasse

(1) Conditions mutuellement exclusives : une ressource ne peut être utilisée que par un seul processus à la fois.
(2) Conditions de demande et de conservation : lorsqu'un processus est bloqué en raison d'une demande de ressources, il conservera les ressources obtenues.
(3) Conditions de non-privation : Les ressources obtenues par le processus ne peuvent pas être privées de force avant d'être épuisées.
(4) Condition d'attente circulaire : une relation de ressources d'attente cyclique tête-à-queue est formée entre plusieurs processus.
L’impasse peut être évitée en brisant une ou plusieurs de ces conditions.

Six, verrouiller (verrouiller)

1. Concept

À partir du JDK5.0, Java fournit un mécanisme de synchronisation des threads plus puissant : la synchronisation est obtenue en définissant explicitement un objet de verrouillage de synchronisation, et le verrou de synchronisation utilise un objet Lock pour agir.
L'interface java.util.concurrent.locks.Lock est un outil permettant de contrôler l'accès aux ressources partagées par plusieurs threads.
Le verrou fournit un accès exclusif aux ressources partagées. Un seul thread peut verrouiller l'objet Lock à la fois, et le thread doit obtenir l'objet Lock avant de commencer à accéder à la ressource partagée.
La classe ReentrantLock implémente Lock, qui a la même concurrence et la même sémantique de mémoire que synchronisé. Dans l'implémentation du contrôle thread-safe, ReentrantLock est plus couramment utilisé, qui peut explicitement verrouiller et libérer les verrous.

2. La comparaison entre synchronisé et Lock

(1) Le verrou est un verrou explicite (ouvrez et fermez le verrou manuellement) et synchronisé est un verrou implicite, qui est automatiquement libéré lorsqu'il sort de la portée.
(2) Le verrouillage n'a que des verrous de bloc de code, et synchronisé a des verrous de bloc de code et des verrous de méthode.
(3) En utilisant le verrou Lock, la JVM passera moins de temps à planifier les threads et aura de meilleures performances. Et il a une meilleure évolutivité (fournit plus de sous-classes) verrouillage
prioritaire > bloc de code de synchronisation (est entré dans le corps de la méthode et a alloué les ressources correspondantes) > méthode de synchronisation (en dehors du corps de la méthode)

3. Échantillon

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();
            }
        }
    }
}

résultat
insérez la description de l'image ici

Sept, coopération de fil

1. Méthode du tube

Producteur : le module chargé de produire les données (peut être une méthode, un objet, un thread, un processus)
Consommateur : le module chargé du traitement des données (peut être une méthode, un objet, un thread, un processus)
buffer : le consommateur ne peut pas utiliser directement les données du producteur, il y a un tampon entre les deux.
Le producteur place les données produites dans le tampon et le consommateur les retire du tampon.

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;
    }


}

résultat
insérez la description de l'image ici

2. Méthode lumineuse de signalisation

Contrôlé par des bits de drapeau

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;
    }
}

résultat
insérez la description de l'image ici

3. Pool de threads

3.1 Contexte

Les ressources fréquemment créées et détruites et utilisant une grande quantité de ressources, telles que les threads dans des situations simultanées, ont un impact important sur les performances.

3.2 Idées

Créez plusieurs threads à l'avance, placez-les dans le pool de threads, obtenez-les directement lorsque vous les utilisez et remettez-les dans le pool après utilisation. Cela peut éviter la création et la destruction fréquentes et permettre la réutilisation.

3.3 Avantages

(1) Améliorer la vitesse de réponse (réduire le temps de création de nouveaux threads)
(2) Réduire la consommation de ressources (réutiliser les threads dans le pool de threads, pas besoin de créer à chaque fois)
(3) Faciliter la gestion des threads
corePoolSize : la taille du noyau pool
maximumPoolSize : nombre maximum de threads
keepAliveTime : combien de temps un thread reste-t-il lorsqu'il n'y a pas de tâche au maximum avant qu'il ne soit terminé

3.4 ExecutorService et exécuteurs

ExecutorService : La véritable interface du pool de threads (sous-classe commune ThreadPoolExecutor)
void exécuter (commande Runnable) : Exécuter des tâches/commandes, sans valeur de retour, généralement utilisées pour exécuter Runnable.
Future submit(Callable task) : exécute la tâche, a une valeur de retour et est généralement utilisé pour exécuter Callable.
void shutdown() : Ferme le pool de connexions.
Exécuteurs : classes d'outils, classes d'usine pour les pools de threads, utilisées pour créer et renvoyer différents types de pools de threads.

3.5 Échantillon

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());
    }
}

résultat
insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/qq_46106857/article/details/128204183
conseillé
Classement