zoukankan      html  css  js  c++  java
  • 从java字节码角度看线程安全性问题

    先看代码:

    package com.roocon.thread.t3;
    
    public class Sequence {
        private int value;
    
        public int getNext(){
            return value++;
        }
    
        public static void main(String[] args) {
            Sequence sequence = new Sequence();
            new Thread(new Runnable() {
                @Override
                public void run() {
                   while (true){
                       System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
                       try {
                           Thread.sleep(100);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }

    运行结果:仔细发现,出现了两个84,但代码想要的结果是,每个线程每次执行,就在原来的基础上加一。因此,这里就是线程的安全问题。

    Thread-0 0
    Thread-1 1
    Thread-2 2
    ...
    Thread-2 81
    Thread-1 82
    Thread-0 83
    Thread-2 84
    Thread-1 84
    Thread-0 85
    Thread-2 86

    解释原因:

    return value++; 通过字节码分析,它其实不是原子操作,value = value + 1;首先,要先读取value的值,然后再对value的值加1,最后将value+1后的结果赋值给原来的value。

    如果有线程1和线程2,假设value此时为83。

    1.线程1读取value的值,为83。

    2.线程1对value进行加1操作,得到值是84,但此时cpu被线程2抢走了,线程2还没来得及将计算后的值赋值给原来的value。

    3.线程2读取value的值,仍然为83。

    4.线程2对value进行加1操作,得到84,此时cpu被线程1抢走了,线程1继续执行赋值操作,将它计算得到的结果值84赋值给value,于是,线程1输出了84。

    5.线程2此时再次抢到了cpu执行权,于是,将它计算得到的结果值84赋值给value,最后输出84。

    下面来查看字节码文件验证:

    继续往下查看字节码文件的getNext方法:

    这些指令告诉我们,value++并不是原子操作。其中,getfield就代表读取value这个字段的值,iadd就表示对value值进行加1操作,而putfield就代表将jia1操作得到的值赋值给原来的value。

    那么,如何解决上面的问题呢?如何保证多线程的安全性问题呢?

    最简单的办法就是,加同步锁。

    package com.roocon.thread.t3;
    
    public class Sequence {
        private int value;
    
        public synchronized int getNext(){
            return value++;
        }
    
        public static void main(String[] args) {
            Sequence sequence = new Sequence();
            new Thread(new Runnable() {
                @Override
                public void run() {
                   while (true){
                       System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
                       try {
                           Thread.sleep(100);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }

    运行结果:

    Thread-0 0
    Thread-1 1
    Thread-2 2
    ...
    Thread-0 81
    Thread-1 82
    Thread-2 83
    Thread-0 84
    Thread-1 85
    Thread-2 86
    Thread-0 87

    解决线程安全性问题有很多解决方案,因为,如果所有的解决方案都是加同步锁,那么,所谓的多线程并发最后变成了串行了。那么,多线程就显得没意义了。

    最后,总结下何时会有如上线程安全性问题:

    1.多线程环境下。

    2.多个线程共享一个资源。如servlet就不是线程安全的。在它的service方法中操作同一个实例变量,如果多个线程同时访问,由于多个线程共享该变量,因此存在线程安全问题。

    3.对线程进行非原子性操作。

  • 相关阅读:
    如何进行有效沟通避免出现误会
    如何进行有效沟通
    怎样提高自己的团队合作能力
    javaScript简介
    css文本格式详解
    css简介及相关概念
    WebGL10---3D模型的加载与使用
    Canvas绘图与动画详解
    Canvas绘制时钟
    WebGL9----将canvas作为纹理,将动画作为纹理(2)
  • 原文地址:https://www.cnblogs.com/sunnyDream/p/8003727.html
Copyright © 2011-2022 走看看