Maison > Java > javaDidacticiel > Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

WBOY
Libérer: 2023-05-07 14:46:08
avant
1170 Les gens l'ont consulté

    Statut du thread en java

    Au niveau du système d'exploitation, un thread a deux états : prêt et bloqué

    Mais en java, afin de connaître rapidement la raison pour laquelle un thread est bloqué lorsque le thread est bloqué. , Le statut de blocage est encore affiné.

    Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

    • NOUVEAU : L'objet thread a été créé, mais le thread au niveau du système n'a pas été créé, ou l'objet thread n'a pas appelé start()

    • . TERMINÉ : le thread dans le système a été détruit, mais l'objet thread dans le code est toujours là, c'est-à-dire qu'une fois l'exécution de run() terminée, l'objet Thread est toujours là

    • RUNNABLE : le thread est dans le file d'attente prête et peut être planifié et exécuté par le CPU à tout moment

    • TIMED_WAITING : pendant l'exécution du thread, l'objet thread appelle sleep() et entre en blocage. Lorsque le temps de veille est écoulé, il reviendra dans la file d'attente prête

      .
    • BLOQUÉ : après qu'un thread a verrouillé (synchronisé) un objet, si un autre thread souhaite également verrouiller cet objet, il tombera dans l'état BLOQUÉ. Ce n'est que lorsque le premier thread déverrouille l'objet verrouillé que ce dernier thread pourra verrouiller l'objet.

    • WAITING : Utilisé avec synchronisé En utilisant wait(), une fois qu'un thread appelle wait(), l'objet sera déverrouillé en premier. Après qu'un autre thread ait exécuté notify(), le thread en attente sera réveillé. définissez également une valeur dans wait(). Le temps d'attente maximum pour éviter les attentes mortes

    Analyse de cas de problèmes de sécurité des threads

    Plusieurs threads écrivant dans la même variable

    1. Concept : Quand une chaîne de code est-elle appelée. un problème de sécurité des threads ? Tout d'abord, les threads. La source des problèmes de sécurité est que lorsque plusieurs threads s'exécutent simultanément, il y aura une exécution préemptive ici. Quand une chaîne de code est-elle appelée un problème de sécurité des threads ? les threads sont simultanés, quelle que soit la manière dont plusieurs threads exécutent leur code de manière préventive, cela n'affectera pas le résultat final, appelé sécurité des threads. Cependant, en raison de l'exécution préemptive, des résultats différents de ceux attendus apparaissent, ce que l'on appelle un problème de sécurité, un bug. !

    2. Cas typique : utilisez deux threads pour effectuer une opération d'auto-incrémentation sur le même nombre 100 000 fois :

    public class Demo1 {
        private static int count=0;
        public static void main(String[] args) {
            Thread t1=new Thread(()->{
                for(int i=0;i<50000;i++){
                    count++;
                }
            });
            t1.start();
            Thread t2=new Thread(()->{
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(count);
        }
    }
    //打印结果:68994
    Copier après la connexion

    Évidemment, le résultat attendu est de 100 000, mais le calcul est supérieur à 6 000 000. C'est ce qui s'est passé. Sécurité des threads.

    Analyse des raisons :

    Effectuez une opération d'auto-incrémentation uniquement sur le nombre de tas de chaque thread : Tout d'abord, vous devez comprendre qu'il y a trois étapes pour qu'une instruction machine effectue une auto-incrémentation. : Obtenez la valeur de comptage de la mémoire principale vers le registre du processeur ->incrémentez la valeur de comptage dans le registre de 1->actualisez la valeur de comptage dans le registre dans la mémoire principale. Appelons ces trois étapes : charger->. ;add->save

    Nous supposons que deux ensembles d'instructions sont exécutés simultanément sur un seul CPU (il est préférable de dessiner deux CPU) (il n'y aura pas de chargement simultané) :

    Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

    Comme la situation dans le image ci-dessus :

    Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

    L'observation a révélé que les deux threads ont exécuté count++ une fois, mais les résultats des deux ++ n'étaient pas satisfaisants, ce qui équivaut à un seul incrément automatique. Ce qui précède est un problème de sécurité des threads.

    Et. nous pouvons prédire La plage de résultats du code ci-dessus est : entre 5w-10w !, pourquoi ?

    Les deux images ci-dessus montrent la situation dans laquelle des problèmes de sécurité des threads se produisent, le résultat est que deux ajouts sont utilisés comme un seul. a été dans cet état (également le pire état), mais le résultat du calcul est 5w. Ensuite, si les deux threads ont été un thread complètement exécuté en charge-ajout-sauvegarde, l'autre thread exécutera alors cette opération, alors elle sera exécutée. en série, ce qui n'est pas 10w.

    3. Comment résoudre le cas ci-dessus ?

    Le cas est également mentionné à la fin que tant que l'exécution en série peut être réalisée, l'exactitude des résultats peut être garantie, alors java l'a. une telle fonction que nous pouvons utiliser, c'est-à-dire l'utilisation du mot-clé synchronisé

    Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

    C'est-à-dire : cpu1 verrouille l'objet de verrouillage avant d'exécuter le chargement, puis le déverrouille après l'enregistrement. Ce n'est qu'alors que cpu2 peut verrouiller le. objet et effectuer une série d'opérations. À ce stade, l'atomicité de chargement-ajout-sauvegarde est assurée, de sorte que ces trois étapes ne soient pas exécutées et que l'exécution soit terminée en une seule fois.

    Ensuite, vous pouvez demander, quelle est la différence entre cela et l'utilisation d'un seul thread principal pour calculer l'auto-incrémentation 100 000 fois ? Quel est l'intérêt de créer plusieurs threads ?

    意义很大,因为我们创建的线程很多时候不仅仅只是一个操作,光针对自增我们可以通过加锁防止出现线程安全问题,但是各线程的其他操作要是不涉及线程安全问题那就可以并发了呀,那此时不就大大提升了执行效率咯.

    4.具体如何加锁呢?

    此处先只说一种加锁方式,先把上述案例的问题给解决了再说.

    使用关键字synchronized,此处使用的是给普通方法加synchronized修饰的方法(除此之外,synchronized还可以修饰代码块和静态方法)

    class Counter{
        private int count;
        synchronized public void increase(){
            this.count++;
        }
        public int getCount(){
            return this.count;
        }
    }
    public class Demo2 {
        private static int num=50000;
        public static void main(String[] args) {
            Counter counter=new Counter();//此时对象中的count值默认就是0
            Thread t1=new Thread(()->{
                for (int i = 0; i < num; i++) {
                    counter.increase();
                }
            });
            t1.start();
    
            Thread t2=new Thread(()->{
                for (int i = 0; i < num; i++) {
                    counter.increase();
                }
            });
            t2.start();
    
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(counter.getCount());
        }
    }//打印10W
    Copier après la connexion

    内存可见性问题

    首先说明:这是有编译器优化导致的,其次要知道cpu读取变量时:先从主内存将变量的值存至缓存或者寄存器中,cpu计算时再在寄存器中读取这个值.

    当某线程频繁的从内存中读取一个不变的变量时,编译器将会把从内存获取变量的值直接优化成从寄存器直接获取.之所以这样优化,是因为,cpu从主内存中读取一个变量比在缓存或者寄存器中读取一个变量的值慢成千上万倍,如果每每在内存中读到的都是同一个值,既然缓存里头已经有这个值了,干嘛还大费周折再去主内存中进行获取呢,直接从缓存中直接读取就可以了,可提升效率.

    但是:一旦一个线程被优化成上述的情况,那如果有另一个线程把内存中的值修改了,我被优化的线程还傻乎乎的手里拿着修改之前的值呢,或者内存中的变量值被修改了,被优化的线程此时已经感应不到了.

    具体而言:

    public class Demo3 {
        private static boolean flag=false;
        public static void main(String[] args) {
            Thread t1=new Thread(()->{
                while(!flag){
                    System.out.println("我是优化完之后直接读取寄存器中的变量值才打印的哦!");
                }
            });
            t1.start();
    
            flag=true;
            System.out.println("我已经在主线程中修改了标志位");
        }
    }
    Copier après la connexion

    运行上述代码之后,程序并不会终止,而是一直在那打印t1线程中的打印语句.

    如何解决上述问题:

    引入关键字volatile:防止内存可见性问题,修饰一个变量,那某线程想获取该变量的值的时候,只能去主内存中获取,其次它还可以防止指令重排序,指令重排问题会在线程安全的单例模式(懒汉)进行介绍.具体:

    public class Demo3 {
        private static volatile boolean flag=false;
        public static void main(String[] args) {
            Thread t1=new Thread(()->{
                while(!flag){
                    System.out.println("我是优化完之后直接读取寄存器中的变量值才打印的哦!");
                }
            });
            t1.start();
    
            try {
                Thread.sleep(1);//主线程给t1留有充足的时间先跑起来
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag=true;
            System.out.println("我已经在主线程中修改了标志位");
        }
    }
    //打印若干t1中的打印语句之后,主线程main中修改标志位之后,可以终止t1
    Copier après la connexion

    注意:上述优化现象只会出现在频繁读的情况,如果不是频繁读,就不会出现那样的优化.

    指令重排序问题

    生活案例:买菜

    Statut du thread, problèmes de sécurité des threads et utilisation du mot-clé synchronisé en Java

    如果是傻乎乎的按照菜单从上到下的去买菜,从路线图可以看出,不必要的路是真的没少走.

    如果执行代码时,编译器认为某些个代码调整一下顺序并不会影响结果,那代码的执行顺序就会被调整,就比如可以把上面买菜的顺序调整成:黄瓜->萝卜->青菜->茄子

    单线程这样的指令重排一般不会出现问题,但是多线程并发时,还这样优化,就容易出现问题

    针对这样的问题,如果是针对一个变量,我们可以使用volatile修饰,如果是针对代码块,我们可以使用synchronized.

    synchronized的用法

    • synchronized起作用的本质

    • 修饰普通方法

    • 修饰静态方法

    • 修饰代码块

    synchronized起作用的本质

    因为我们知道java中所有类都继承了Object,所以所有类都包含了Object的部分,我们可以称这继承的部分是"对象头",使用synchronized进行对象头中的标志位的修改,就可以做到一个对象的锁一个时刻只能被一个线程所持有,其他线程此时不可抢占.这样的设置,就好像把一个对象给锁住了一样.

    修饰普通方法

    如前述两个线程给同一个count进行自增的案例.不再赘述.此时的所对象就是Counter对象

    修饰静态方法⚡️

    与普通方法类似.只不过这个方法可以类名直接调用.

    修饰代码块

    首先修饰代码块需要执行锁对象是谁,所以这里可以分为三类,一个是修饰普通方法的方法体这个代码块的写法,其次是修饰静态方法方法体的写法,最后可以单独写一个Object的对象,来对这个Object对象进行上锁.

    class Counter{
        private int count;
        public void increase(){
            synchronized(this){
                count++;
            }
        }
        public int getCount(){
            return this.count;
        }
    }
    Copier après la connexion
    class Counter{
        private static int count;
        public static void increase(){
            synchronized(Counter.class){//注意这里锁的是类对象哦
                count++;
            }
        }
        public int getCount(){
            return this.count;
        }
    }
    Copier après la connexion
    class Counter{
        private static int count;
        private static Object locker=new Object();
        public static void increase(){
            synchronized(locker){
                count++;
            }
        }
        public int getCount(){
            return this.count;
        }
    }
    Copier après la connexion

    注意:java中这种随手拿一个对象就能上锁的用法,是java中一种很有特色的用法,在别的语言中,都是有专门的锁对象的.

    Conclusion

    java中的线程状态,以及如何区分线程安全问题 罪恶之源是抢占式执行多线程对同一个变量进行修改,多线程只读一个变量是没有线程安全问题的修改操作是非原子性的内存可见性引起的线程安全问题指令重排序引起的线程安全问题 synchronized的本质和用法

    1.java中的线程状态,以及如何区分
    2.线程安全问题

    • 罪恶之源是抢占式执行

    • Plusieurs threads modifient la même variable, et il n'y a pas de problème de sécurité des threads lorsque plusieurs threads ne lisent qu'une seule variable

    • L'opération de modification est non atomique

    • Problèmes de sécurité des threads causés par la visibilité de la mémoire

    • Problèmes de sécurité des fils causés par la réorganisation des instructions

    3. L'essence et l'utilisation de synchronisé

    .

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Étiquettes associées:
    source:yisu.com
    Déclaration de ce site Web
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
    Tutoriels populaires
    Plus>
    Derniers téléchargements
    Plus>
    effets Web
    Code source du site Web
    Matériel du site Web
    Modèle frontal