多线程(四):线程安全

观察线程安全和线程不安全

观察线程安全

单线程是线程安全的,示例代码如下:

public class ThreadDemo25 {      static class Counter {         //定义私有变量         private int num = 0;         //定义任务执行次数         private final int maxSize = 100000;          //num++         public void incrment() {             for (int i = 0; i < maxSize; i++) {                 num++;             }         }          //num--         public void decrment() {             for (int i = 0; i < maxSize; i++) {                 num--;             }         }          public int getNum() {             return num;         }     }      public static void main(String[] args) {         Counter counter = new Counter();         counter.incrment();         counter.decrment();         System.out.println("最终执行结果:" + counter.getNum());     } } 

该代码的执行结果如下:
多线程(四):线程安全

观察线程不安全

多线程可能是线程不安全的,示例代码如下:

public class ThreadDemo26 {      static class Counter {         //定义私有变量         private int num = 0;         //定义任务执行次数         private final int maxSize = 100000;          //num++         public void incrment() {             for (int i = 0; i < maxSize; i++) {                 num++;             }         }          //num--         public void decrment() {             for (int i = 0; i < maxSize; i++) {                 num--;             }         }          public int getNum() {             return num;         }     }      public static void main(String[] args) throws InterruptedException {         Counter counter = new Counter();          Thread t1 = new Thread(() -> {             counter.incrment();         });         t1.start();          Thread t2 = new Thread(() -> {             counter.decrment();         });         t2.start();                  t1.join();         t2.join();          System.out.println("最终执行结果:" + counter.getNum());     } } 

若是线程安全的话,代码执行结果,应该是与前者一致的,但实际执行结果如下:
多线程(四):线程安全

线程安全概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的;反之,这个程序就是线程不安全的。

导致线程不安全的原因

比如刚才我们看到的 num++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

不保证原子性会给多线程带来的问题:
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
多线程(四):线程安全
原子性:
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
一条 java 语句不一定是原子的,也不一定只是一条指令。

不保证原子性会给多线程带来什么问题
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。

示例代码如下:

public class ThreadDemo27 {     private static boolean flag = false;      public static void main(String[] args) {         Thread t1 = new Thread(new Runnable() {             @Override             public void run() {                 while (!flag) {                  }                 System.out.println("终止执行");             }         });         t1.start();          Thread t2 = new Thread(new Runnable() {             @Override             public void run() {                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println("设置flag = true");                 flag = true;             }         });         t2.start();     } } 

代码执行结果如下:
多线程(四):线程安全
我们发现,该代码的预期执行结果,应该是当运行flag = true;代码时,t1线程的while循环条件不满足,跳出循环并执行System.out.println("终止执行");,但是,由于内存不可见,造成t1线程中while死循环现象。

多线程(四):线程安全

线程非安全的原因:

  1. CPU抢占 执行(万恶之源)
  2. 非原子性,如图示
  3. (内存)不可见
  4. 编译器优化(代码优化)
  5. 多个线程修改了同一个变量

编译器优化:在单线程下执行,没有问题,可以提高程序执行的效率;但是在多线程下就会出现混乱,从而导致线程不安全。

演示:多线程操作并未修改相同变量,那么线程也是安全的,代码如下,

public class ThreadDemo28 {      static class Counter {         //定义任务执行次数         private final int maxSize = 100000;          //num++         public int incrment() {             int num1 = 0;             for (int i = 0; i < maxSize; i++) {                 num1++;             }             return num1;         }          //num--         public int decrment() {             int num2 = 0;             for (int i = 0; i < maxSize; i++) {                 num2--;             }             return num2;         }       }      private static int num1 = 0;     private static int num2 = 0;      public static void main(String[] args) throws InterruptedException {         Counter counter = new Counter();          Thread t1 = new Thread(() -> {             num1 = counter.incrment();         });         t1.start();          Thread t2 = new Thread(() -> {             num2 = counter.decrment();         });         t2.start();          t1.join();         t2.join();          System.out.println("最终执行结果:" + (num1 + num2));     } } 

该代码执行结果如下:
多线程(四):线程安全
可见,该程序的多线程是安全的。

版权声明:玥玥 发表于 2021-05-10 16:44:43。
转载请注明:多线程(四):线程安全 | 女黑客导航