多线程的安全问题

一、可见性

  • 例如下面的程序,先启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值。
    //1.线程类
            public class MyThread extends Thread {
                public static int a = 0;
                @Override
                public void run() {
                    System.out.println("线程启动,休息2秒...");
                    try {
                        Thread.sleep(1000 * 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("将a的值改为1");
                    a = 1;
                    System.out.println("线程结束...");
                }
            }
    //测试类
    public class Demo {
    public static void main(String[] args) {
    MyThread t = new MyThread();
    t.start();
       //主线程继续
    while (true) {
    if (MyThread.a == 1) {
    System.out.println("主线程读到了a = 1");
    }
    }
    }
    }

二、有序性

  • 有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:
           int a = 10; //1
           int b = 20; //2
           int c = a + b; //3
         第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会
         将1,2编译完毕。1和2先编译谁,不影响第三行的结果。
  • 但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响:

三、原子性

  • 请看以下示例
//线程类
public class MyThread extends Thread {
    public static int a = 0;
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
        }
        System.out.println("修改完毕!");
    }
}
//测试类
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        //总是不准确的。原因:两个 线程访问a的步骤不具有:原子性
        System.out.println("获取a最终值:" + MyThread.a);
    }
}
  • 内存工作图

  

 线程t1先读取a 的值为:0

t1被暂停

线程t2读取a的值为:0
t2将a = 1
t2将a写回主内存
t1将a = 1
t1将a写回主内存(将t2更改的1,又更改为1)
所以两次加1,但结果仍为1,少加了一次。
原因:两个线程访问同一个变量a的代码不具有"原子性" 
 

四、解决

  volatile关键字----解决可见性和有序性
  原子类、synchronized----解决原子性、有序性、可见性
posted @ 2020-07-25 14:44  德华。  阅读(176)  评论(0编辑  收藏  举报