Java--synchronisé et verrouillage du multithreading ; interblocage (4)

Java--concurrence multithreading, parallélisme, processus, thread (1) - Blog de MinggeQingchun - Blog CSDN

Java -- Thread de terminaison/interruption multi-thread (2) - Blog de MinggeQingchun - Blog CSDN - Thread d'interruption Java

Java--join, yield, sleep of multithreading ; priorité des threads ; temporisateur ; thread démon (3) - Blog de MinggeQingchun - Blog CSDN

Java – Mode consommateur producteur multithread ; pool de threads ExecutorService (5) - Blog de MinggeQingchun - Blog CSDN

un, synchronisé

Avant de comprendre la synchronisation, examinons un exemple de thread non sécurisé

Par exemple, s'il y a 10 000 yuans sur un compte, deux personnes retirent de l'argent en même temps, ce qui entraînera une erreur de solde, ou l'argent retiré est supérieur au montant du compte.

public class AccountThreadTest {
    public static void main(String[] args) {
        Account account = new Account("xiaoming",10000.0);

        AccountThread t1 = new AccountThread(account);
        AccountThread t2 = new AccountThread(account);

        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class AccountThread extends Thread{
    private Account account;

    public AccountThread(Account account){
        this.account = account;
    }

    @Override
    public void run() {
        double money = 5000.0;

        account.withDraw(money);

        System.out.println(Thread.currentThread().getName() + "在" + account.getAccount() + "账户成功取款" + money + ";余额为" + account.getBalance());
    }
}

class Account {
    private String account;
    private Double balance;

    public Account(){

    }

    public Account(String account,Double balance){
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    //取钱
    public void withDraw(Double money){
        Double afterMoney = this.getBalance() - money;

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

        this.setBalance(afterMoney);
    }
}

La sortie est la suivante

t2在xiaoming账户成功取款5000.0;余额为5000.0
t1在xiaoming账户成功取款5000.0;余额为5000.0

Les threads t1 et t2 retirent respectivement 5 000 du même compte avec un montant total de 10 000, mais le solde est toujours de 5 000, ce qui entraîne une insécurité des données en cas de multithreading simultané

(1) Problèmes de sécurité dans un environnement simultané multithread

1. Problèmes de sécurité des données simultanées multithreads

Une fois les trois conditions suivantes remplies, il y aura des problèmes de sécurité des threads

1. Concurrence multithread

2. Il y a des données partagées

3. Les données partagées ont un comportement de modification

2. Solution au problème de sécurité des données simultanées multithread : mécanisme de synchronisation des threads

Le mécanisme de synchronisation des threads est la mise en file d'attente des threads pour l'exécution, et la mise en file d'attente des threads pour l'exécution sacrifiera une partie de l'efficacité de l'exécution, mais la sécurité des données est la première, et la sécurité des données ne peut parler que d'efficacité ; les données ne sont pas sûres et l'efficacité ne peut pas être discutée

Dans le langage Java, le principal moyen d'assurer la sécurité des threads est le verrouillage, et il existe deux principaux types de verrous en Java : synchronisé et verrouillé.

(2) synchronisé

Synchronized est un mot clé en Java. Son implémentation est basée sur les instructions jvm . Avant JDK1.5, nous utilisions synchronized pour réaliser la synchronisation des threads sans exception lors de l'écriture de programmes concurrents, et synchronized était implémenté dans JDK1 Avant .5, la surcharge de synchronisation était élevée et l'efficacité était faible. Par conséquent, après JDK1.5, l'interface de verrouillage au niveau du code (synchronisé est au niveau jvm) a été introduite pour réaliser le verrouillage de synchronisation avec la même fonction que

Premier regard sur synchronisé

L'explication officielle de la synchronisation est la suivante

Les méthodes de synchronisation synchronisées peuvent prendre en charge l'utilisation d'une stratégie simple pour éviter les interférences de threads et les erreurs de cohérence mémoire : si un objet est visible par plusieurs threads, toutes les lectures ou écritures dans les variables d'objet sont effectuées via des méthodes synchronisées.

En termes simples, le rôle de Synchronized est la méthode la plus couramment utilisée et la plus simple pour résoudre les problèmes de concurrence en Java. Il peut garantir qu'au plus un thread exécute le code de synchronisation en même temps, garantissant ainsi l'effet de la sécurité de la concurrence dans un multi-thread. environnement. Si un morceau de code est modifié par Synchronized, alors ce code sera exécuté de manière atomique. Lorsque plusieurs threads exécutent ce code, ils s'excluent mutuellement, n'interféreront pas les uns avec les autres et ne s'exécuteront pas en même temps

Le mécanisme de fonctionnement de Synchronized consiste à utiliser un verrou dans un environnement multithread. Lorsque le premier thread s'exécute, acquérez le verrou avant qu'il ne puisse être exécuté. Une fois acquis, il monopolisera le verrou jusqu'à ce que l'exécution soit terminée ou il ne sera pas libéré sous certaines conditions. Ce verrou, les autres threads ne peuvent que bloquer et attendre que le verrou soit libéré

Synchronized est un mot-clé en Java, pris en charge nativement par Java, et est le verrou de synchronisation le plus basique

Les objets modifiés par synchronized sont les suivants : 

1. Modifier un bloc de code. Le bloc de code modifié est appelé bloc d'instructions synchrones. Son champ d'action est le code entre accolades {} et l'objet de l'action est l'objet qui appelle ce bloc de code.

2. Modifier une méthode. La méthode modifiée est appelée méthode synchrone. Son périmètre d'action est l'ensemble de la méthode et l'objet de l'action est l'objet qui appelle cette méthode.

3. Pour modifier une méthode statique, la portée de son effet est l'ensemble de la méthode statique et l'objet de l'action est tous les objets de cette classe

4. Pour modifier une classe, la portée de son effet est la partie entre parenthèses après synchronisation, et l'objet principal de l'action est tous les objets de cette classe

1. Modifier la méthode commune

/**
 * synchronized 修饰普通方法
 */
public synchronized void method() {
    // ....
}

Lorsque la synchronisation modifie une méthode ordinaire, la méthode modifiée est appelée une méthode synchronisée, et sa portée d'action est la méthode entière, et l'objet de l'action est l'objet appelant cette méthode this

2. Méthode statique modifiée

/**
 * synchronized 修饰静态方法
 */
public static synchronized void staticMethod() {
    // .......
}

Lorsque la synchronisation modifie une méthode statique, son champ d'action est l'ensemble du programme, et ce verrou est mutuellement exclusif pour tous les objets qui appellent ce verrou 

Pour les méthodes statiques, le verrouillage synchronisé est global, c'est-à-dire que pendant toute l'exécution du programme, tous les objets qui appellent cette méthode statique s'excluent mutuellement, tandis que les méthodes ordinaires visent le niveau de l'objet et différents objets correspondent à différents verrous.

Le verrouillage de méthode statique est global et cible tous les appelants ; le verrouillage de méthode commun se situe au niveau de l'objet et différents objets ont des verrous différents

Verrou d'objet : 1 verrou pour 1 objet, 100 verrous pour 100 objets
Verrou de classe : 100 objets, ou juste 1 verrou de classe

3. Modifier le bloc de code

En développement quotidien, le plus couramment utilisé est de verrouiller le bloc de code au lieu de la méthode, car verrouiller la méthode équivaut à verrouiller la méthode entière. Dans ce cas, la granularité du verrou est trop grande, et l'exécution du programme Les performances seront affectées, donc dans des circonstances normales, nous utiliserons la synchronisation pour verrouiller le bloc de code, et sa syntaxe d'implémentation est la suivante

public void classMethod() throws InterruptedException {
    // 前置代码...
    
    // 加锁代码
    synchronized (SynchronizedUsage.class) {
        // ......
    }
    
    // 后置代码...
}

Par rapport à la méthode modifiée, le bloc de code modifié doit spécifier manuellement l'objet verrouillé, et l'objet verrouillé est généralement exprimé sous la forme de this ou xxx.class

// 加锁某个类
synchronized (SynchronizedUsage.class) {
    // ......
}

// 加锁当前类对象
synchronized (this) {
    // ......
}

cette classe VS

L'utilisation de synchronized pour verrouiller ceci et xxx.class est complètement différente. Lorsque vous verrouillez ceci, cela signifie que l'objet actuel est utilisé pour le verrouillage et que chaque objet correspond à un verrou ; lorsque vous utilisez xxx.class pour verrouiller, cela signifie Utiliser une classe ( plutôt qu'une instance de classe) à verrouiller, qui est au niveau de l'application et est globalement efficace

Il peut être distingué des quatre exemples suivants

/**
 doOther方法执行的时候需要等待doSome方法的结束吗?
 不需要,因为doOther()方法没有synchronized
 */
public class SynchronizedExam {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();

        MyThread t1 = new MyThread(myClass);
        t1.setName("t1");

        MyThread t2 = new MyThread(myClass);
        t2.setName("t2");

        t1.start();
        // 睡眠的作用:保证t1线程先执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass myClass;

    public MyThread(){
    }

    public MyThread(MyClass myClass){
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass {
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }

    public void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

sortir

doSome begin
doOther begin
doOther over
doSome over
/**
 doOther方法执行的时候需要等待doSome方法的结束吗?
 需要,doOther()方法有synchronized
 */
public class SynchronizedExam {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();

        MyThread t1 = new MyThread(myClass);
        t1.setName("t1");

        MyThread t2 = new MyThread(myClass);
        t2.setName("t2");

        t1.start();
        // 睡眠的作用:保证t1线程先执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass myClass;

    public MyThread(){
    }

    public MyThread(MyClass myClass){
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass {
    // 普通的同步方法 锁的调用者,如 this对象,obj对象; Object obj = new Object();
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }

    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

sortir

doSome begin
doSome over
doOther begin
doOther over
/**
 doOther方法执行的时候需要等待doSome方法的结束吗?
 不需要,因为MyClass对象是两个,两把锁
 */
public class SynchronizedExam {
    public static void main(String[] args) {
        MyClass myClass1 = new MyClass();
        MyClass myClass2 = new MyClass();

        MyThread t1 = new MyThread(myClass1);
        t1.setName("t1");

        MyThread t2 = new MyThread(myClass2);
        t2.setName("t2");

        t1.start();
        // 睡眠的作用:保证t1线程先执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass myClass;

    public MyThread(){
    }

    public MyThread(MyClass myClass){
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass {
    // 普通的同步方法 锁的调用者,如 this对象,obj对象; Object obj = new Object();
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }

    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

sortir

doSome begin
doOther begin
doOther over
doSome over
/**
 doOther方法执行的时候需要等待doSome方法的结束吗?
 需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把
 */
public class SynchronizedExam {
    public static void main(String[] args) {
        MyClass myClass1 = new MyClass();
        MyClass myClass2 = new MyClass();

        MyThread t1 = new MyThread(myClass1);
        t1.setName("t1");

        MyThread t2 = new MyThread(myClass2);
        t2.setName("t2");

        t1.start();
        // 睡眠的作用:保证t1线程先执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread{
    private MyClass myClass;

    public MyThread(){
    }

    public MyThread(MyClass myClass){
        this.myClass = myClass;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")){
            myClass.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            myClass.doOther();
        }
    }
}

class MyClass {
    // 静态的同步方法 锁的是 Class 类模板(.class文件;一个类只有唯一的一份.class文件)
    // synchronized 锁的对象是方法的调用者!
    // static 静态方法
    // 类一加载就有了!锁的是Class
    public synchronized static void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }

    public synchronized static void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

sortir

doSome begin
doSome over
doOther begin
doOther over

Pour l'exemple ci-dessus de retrait d'argent, il nous suffit d'ajouter un verrou à l'objet partagé dans Compte

class Account {
    private String account;
    private Double balance;

    // 实例变量Account对象是多线程共享的,Account对象中的实例变量obj也是共享的)
    Object obj = new Object();

    public Account(){

    }

    public Account(String account,Double balance){
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    /**
     线程同步机制的语法是:
     synchronized(){
        // 线程同步代码块
     }
     被synchronized修饰的代码块及方法,在同一时间,只能被单个线程访问
     synchronized()小括号中传的“数据”必须是多线程共享的数据,才能达到多线程排队

     ()中参数
     假设t1、t2、t3、t4、t5,有5个线程
     希望t1 t2 t3排队,t4 t5不需要排队
     在()中写一个t1 t2 t3共享的对象,而这个对象对于t4 t5来说不是共享的

     synchronized()执行原理
     1、假设t1和t2线程并发,开始执行代码时,有一个先后顺序
     2、假设t1先执行,遇到了synchronized,t1自动找“共享对象”的对象锁,
     找到之后并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直
     占有这把锁的,直到同步代码块代码结束,这把锁才会释放
     3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有
     共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
     直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
     t2占有这把锁之后,进入同步代码块执行程序
     */

    //取钱
    public void withDraw(Double money){
//        Object obj2 = new Object();

//        synchronized (obj) {
        //synchronized ("abc") { // "abc"在字符串常量池当中,会被所有线程共享
        //synchronized (null) { // 报错:空指针
//        synchronized (obj2) { // obj2是局部变量,不是共享对象
        synchronized (this){
            Double afterMoney = this.getBalance() - money;

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

            this.setBalance(afterMoney);
        }
    }
}

(3) Principe d'exécution Synchronized()

1. En supposant que les threads t1 et t2 sont simultanés, il y a une séquence lors du démarrage de l'exécution du code

2. En supposant que t1 est exécuté en premier, lorsqu'une synchronisation est rencontrée, t1 trouve automatiquement le verrou d'objet de "l'objet partagé", le trouve et maintient le verrou, puis exécute le programme dans le bloc de code de synchronisation et maintient le verrou tous le temps pendant le processus d'exécution du programme. Ce verrou ne sera libéré qu'à la fin du code de bloc de code synchronisé

3. En supposant que t1 possède déjà ce verrou, t2 rencontre également le mot clé synchronized à ce moment et occupera également ce verrou de l'objet partagé. Par conséquent, ce verrou est occupé par t1 et t2 ne peut attendre que t1 à l'extérieur le bloc de code synchronisé Fin, jusqu'à ce que t1 ait fini d'exécuter le bloc de code synchrone, t1 renverra le verrou, à ce moment t2 attend finalement le verrou, puis t2 prend possession du verrou, puis entre dans le programme d'exécution du bloc de code synchrone

Deux, verrouiller

À partir de JDK 5.0, Java fournit un mécanisme de synchronisation de thread plus puissant - la synchronisation est obtenue en définissant explicitement un objet de verrouillage de synchronisation. Les verrous de synchronisation utilisent les objets Lock comme

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 à la fois peut verrouiller l'objet Lock. Avant que le thread ne commence à accéder à la ressource partagée, il doit d'abord obtenir l'objet Lock.

La classe ReentrantLock implémente Lock, qui a la même sémantique de simultanéité et de mémoire que synchronized. Lors de l'implémentation d'un contrôle thread-safe, ReentrantLock est plus couramment utilisé, qui peut explicitement verrouiller et libérer des verrous.

Comme suit, simulez le processus d'achat de billets

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

        Thread t1 = new Thread(buyTicketThread);
        Thread t2 = new Thread(buyTicketThread);
        Thread t3 = new Thread(buyTicketThread);

        t1.setName("小明");
        t2.setName("小李");
        t3.setName("小张");

        t1.start();
        t2.start();
        t3.start();
    }
}

class BuyTicketThread implements Runnable{
    private int ticketNum = 10;
    private boolean flag = true;

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

    //synchronized 同步方法,锁的是this
    private /*synchronized*/ void buy() {
        if (ticketNum <= 0){
            flag = false;
            return;
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "购买了第" + ticketNum-- + "张票");
    }
}

sortir

小李购买了第9张票
小张购买了第10张票
小明购买了第8张票
小张购买了第5张票
小明购买了第6张票
小李购买了第7张票
小张购买了第4张票
小明购买了第2张票
小李购买了第3张票
小张购买了第0张票
小明购买了第-1张票
小李购买了第1张票

S'il n'est pas verrouillé, plusieurs threads différents achèteront le même ticket, ou même 0 et des nombres négatifs ; si vous ajoutez une modification synchronisée à la méthode d'achat du ticket, vous pouvez vous assurer que l'achat du ticket est normal

import java.util.concurrent.locks.ReentrantLock;

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

        Thread t1 = new Thread(buyTicketThread);
        Thread t2 = new Thread(buyTicketThread);
        Thread t3 = new Thread(buyTicketThread);

        t1.setName("小明");
        t2.setName("小李");
        t3.setName("小张");

        t1.start();
        t2.start();
        t3.start();
    }
}

class BuyTicketThread implements Runnable{
    private int ticketNum = 10;

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

    @Override
    public void run() {
        while (true) {
            try {
                //加锁
                lock.lock();
                if (ticketNum > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "购买了第" + ticketNum-- + "张票");
                }
            }finally {
                //解锁
                lock.unlock();
            }
        }
    }
}

Le verrou verrouille l'objet Lock. Lorsque sa méthode de verrouillage est appelée, un état d'indicateur dans la classe Lock est augmenté de 1 (l'état est en fait une variable dans la classe AbstractQueuedSynchronizer, qui est la classe parent d'une classe interne dans Lock. ) , lorsque le verrou est relâché, l'état est décrémenté de 1 (l'opération d'ajout de 1 et de décrémentation de 1 consiste à réaliser la réentrance)

Note:

Le verrou est un verrou explicite, et le verrou doit être fermé manuellement (ouvrir et fermer manuellement le verrou, oublier de fermer le verrou entraînera un blocage) 

Le verrou doit être fermé et libéré manuellement, et doit être libéré dans la clause finally

Commentaires de code source dans la classe ReentrantLock

3. La différence entre synchronisé et verrouillé

1. Lock est une interface ; synchronized est un mot-clé intégré à Java

2. La serrure peut juger si la serrure a été acquise ; la synchronisation ne peut pas juger de l'état de la serrure

3. Le verrou est un verrou explicite et le verrou doit être fermé manuellement (l'ouverture et la fermeture manuelles du verrou, l'oubli de fermer le verrou entraînera un blocage synchronisé est un verrou implicite, qui libère automatiquement le verrou et le libère automatiquement hors de portée

4. La serrure n'a que des verrous de bloc de code, qui conviennent à un grand nombre de blocs de code synchronisés ; synchronisé a des verrous de bloc de code et des verrous de méthode, qui conviennent à une petite quantité de code

5. En utilisant le verrou Lock, la JVM passera moins de temps à planifier les threads et les performances seront meilleures, ce qui n'entraîne pas nécessairement la mise en file d'attente des threads et l'attente du blocage. Et il a une meilleure évolutivité (fournissant plus de sous-classes); la synchronisation entraînera la mise en file d'attente des threads et attendra le blocage

6. Serrure, serrure réentrante, peut juger de l'état de la serrure, injuste ; serrure réentrante synchronisée, ininterruptible, injuste

utiliser la priorité

Verrouiller > Bloc de code synchrone (est entré dans le corps de la méthode et a alloué les ressources correspondantes) > Méthode synchrone (en dehors du corps de la méthode)

Quatrièmement, les problèmes de sécurité des threads des trois principales variables en Java

1. Trois variables en Java

1. Variables d'instance : dans le tas

2. Variables statiques : dans la zone méthode

3. Variables locales : sur la pile

Les variables locales ne peuvent jamais être thread-safe, car les variables locales ne sont pas partagées (une pile par thread). Les variables locales sont sur la pile. Les variables locales ne sont donc jamais partagées

 Les variables d'instance sont dans le tas, et il n'y a qu'un seul tas

 Les variables statiques se trouvent dans la zone de méthode et il n'y a qu'une seule zone de méthode

Le tas et la zone de méthode sont partagés par plusieurs threads, il peut donc y avoir des problèmes de sécurité des threads

Variables locales + constantes : aucun problème de sécurité des threads

Variables membres : il peut y avoir des problèmes de sécurité des threads

2. Utilisez des variables locales

Utilisation recommandée : StringBuilder (thread-unsafe)

Parce que les variables locales n'ont pas de problèmes de sécurité des threads

Choisissez StringBuilder ; StringBuffer (thread-safe, les méthodes dans le code source sont modifiées avec la synchronisation) est relativement inefficace

ArrayList n'est pas thread-safe

Le vecteur est thread-safe

HashMap HashSet n'est pas thread-safe

Hashtable est thread-safe.

3. Résoudre les problèmes de sécurité des threads pendant le développement

Nous ne choisissons pas la synchronisation des threads synchronisée au début du développement ; la synchronisation réduira l'efficacité d'exécution du programme et l'expérience utilisateur n'est pas bonne. Le débit utilisateur du système est réduit. Mauvaise expérience utilisateur. Choisissez le mécanisme de synchronisation des threads en dernier recours

La première solution : essayez d'utiliser des variables locales au lieu de "variables d'instance et variables statiques"

La deuxième solution : Si ce doit être une variable d'instance, alors vous pouvez envisager de créer plusieurs objets, afin que la mémoire de la variable d'instance ne soit pas partagée (un thread correspond à 1 objet, 100 threads correspondent à 100 objets, les objets ne sont pas partagés , il n'y a pas de problème de sécurité des données)

La troisième solution : si les variables locales ne peuvent pas être utilisées et que plusieurs objets ne peuvent pas être créés, pour le moment, vous ne pouvez choisir que synchronisé (mécanisme de synchronisation des threads)

Cinq, impasse

Le thread 1 contient la ressource B et le thread 2 contient la ressource A. Ils veulent tous les deux demander les ressources de l'autre en même temps, donc ces deux threads s'attendront et entreront dans un état de blocage.

Il n'y aura pas de rapport d'erreur ou d'anomalie dans le phénomène de blocage. Le programme a été bloqué là-bas, et il est difficile de le découvrir et de le déboguer.

1. Conditions nécessaires à l'impasse

Pour qu'un interblocage se produise, les quatre conditions suivantes doivent être remplies :

1. Condition d'exclusion mutuelle : la ressource n'est occupée que par un seul thread à la fois

2. Conditions de demande et de maintien : lorsqu'un processus est bloqué en raison de la demande de ressources, il ne lâchera pas les ressources obtenues.

3. Condition de non-privation : les ressources obtenues par le thread ne peuvent pas être privées de force par d'autres threads avant qu'elles ne soient épuisées, et les ressources ne sont libérées qu'après leur épuisement.

4. Condition d'attente circulaire : un certain nombre de processus forment une relation de ressource d'attente circulaire tête-bêche

2. Évitez les blocages de threads

Les quatre conditions nécessaires à l'impasse sont mentionnées ci-dessus. Afin d'éviter l'impasse, il vous suffit de détruire l'une des quatre conditions de l'impasse.

1. Détruire la condition d'exclusion mutuelle : nous n'avons aucun moyen de détruire cette condition, car nous utilisons des verrous pour les rendre mutuellement exclusifs (les ressources critiques nécessitent un accès mutuellement exclusif)

2. Demande de destruction et conditions de maintenance : s'appliquer à toutes les ressources en même temps

3. Conditions de destruction et de non-privation : lorsqu'un thread occupant certaines ressources s'applique à d'autres ressources, si l'application ne peut pas être obtenue, il peut activement libérer les ressources qu'il occupe

4. Destruction des conditions d'attente circulaires : prévenir en demandant des ressources en séquence. Demander des ressources dans un certain ordre et libérer des ressources dans l'ordre inverse. condition d'attente de la boucle d'interruption

//死锁:
public class DeadLock {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();

        MyThread1 t1 = new MyThread1(obj1,obj2);
        MyThread2 t2 = new MyThread2(obj1,obj2);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{
    private Object obj1;
    private Object obj2;

    public MyThread1(){
    }

    public MyThread1(Object obj1,Object obj2){
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    @Override
    public void run() {
        synchronized (obj1){

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

            synchronized (obj2){

            }
        }
    }
}

class MyThread2 extends Thread{
    private Object obj1;
    private Object obj2;

    public MyThread2(){
    }

    public MyThread2(Object obj1,Object obj2){
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    @Override
    public void run() {
        synchronized (obj2){

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

            synchronized (obj1){

            }
        }
    }
}

lien de référence

https://www.jb51.net/article/244365.htm

Je suppose que tu aimes

Origine blog.csdn.net/MinggeQingchun/article/details/127272913
conseillé
Classement