学习一时爽,一直学习一直爽
Hello,大家好,我是 もうり,一个从无到有的技术+语言小白。
在 Java 中多线程永远是一个重要的话题
多线程混乱造成 线程不安全
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
/** * @author: 毛利 */public class DateHolder { private long number = 0; public void change(long delta) { number += delta; } public void print() { System.out.println("Number=" + number); }}
/** * @author: 毛利 */public class ChangeData implements Runnable{ // 私有对象DataHolder private DataHolder dataHolder; private long loopCount; private long delta; // 传递变量 public ChangeData(long delta, long loopCount, DataHolder dataHolder) { this.delta = delta; this.loopCount = loopCount; this.dataHolder = dataHolder; } // 重载run方法 @Override public void run() { for (int i = 0; i < loopCount; i++) { dataHolder.change(delta); } dataHolder.print(); }}
/** * @author: 毛利 */public class SingleThreadSimple { public static void main(String[] args) { // TODO 对一个数据进行相同次数的加减,而且也没有溢出,最后的结果应该是0 DataHolder dataHolder = new DataHolder(); // 4294967294 Integer.MAX_VALUE ChangeData increase = new ChangeData(2, Integer.MAX_VALUE, dataHolder); increase.run(); ChangeData decrease = new ChangeData(-2, Integer.MAX_VALUE, dataHolder); decrease.run(); // 0 }}
但是对于多线程,结果就不一样了
public class MultiThreadChaos { public static void main(String[] args) { // TODO 同样的运算,安排在两个线程里做,结果就不一样了 DataHolder dataHolder = new DataHolder(); // TODO 创建两个线程 increaseThread 和 decreaseThread Thread increaseThread = new Thread(new ChangeData(2, Integer.MAX_VALUE, dataHolder)); Thread decreaseThread = new Thread(new ChangeData(-2, Integer.MAX_VALUE, dataHolder)); System.out.println("执行开始"); increaseThread.start(); decreaseThread.start(); /* 执行开始 Number=-4219868950 Number=-4294408024 */ }}
解决方法:一个 synchronized 解决问题
public synchronized static void changeStatic(long delta) { numberStatic += delta; }
除了 synchronized,还有另一个 volatile 关键字,不仅仅在 Java 语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。
volatile 通常被比喻成 "轻量级的 synchronized",也是 Java 并发编程中比较重要的一个关键字。
和 synchronized 不同,volatile 是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
相比 Sychronized(重量级锁,对系统性能影响较大),volatile 提供了另一种解决 原子性,可见性和有序性
补充几个概念
volatile 都是在 cpu 指令级别进行操作。volatile 是无法保证变量安全的:
volatile 变量只能确保可见性。
java 内存模型 (Java Memory Model,JMM) 是 java 虚拟机规范定义的,用来屏蔽掉 java 程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现 java 程序在各种不同的平台上都能达到内存访问的一致性。
大家都知道计算机都是多核的,在多核中,不仅面临如上问题,还有如果多个核用到了同一个数据,如何保证数据的一致性、正确性等问题,也是必须要解决的。所以协调 cpu 和各个硬件之间的速度差异是非常重要的,要不然 cpu 就一直在等待,浪费资源。
JMM 涉及到的概念有:
来源: 极客时间
public class DataHolder { int a, b, c, d, f, g; long e; // TODO 有 volatile 修饰就会影响之前的指令重排 // volatile long e; public void operateData() { // TODO 按照这个顺序执行,g 的值是肯定小于等于 e 的。但是实际执行在执行的时候,可能会为了优化的目的重排 a += 1; b += 1; c += 1; d += 1; e += 1; f += 1; g += 1; } int counter; // 计算触发重排多少次 public void check() { // TODO 看似不可能的条件,实际可能被触发到 if (g > e) { System.out.println("got it " + (counter++)); } }}
public class VolatileAppMain { public static void main(String[] args) { DataHolder dataHolder = new DataHolder(); Thread operator = new Thread(() -> { while (true) { dataHolder.operateData(); } }); operator.start(); Thread checker = new Thread(() -> { while (true) { dataHolder.check(); } }); checker.start(); }}
从结果来看一直存在指令重排
public class VolatileAppMain { public static void main(String[] args) { DataHolder dataHolder = new DataHolder(); Thread operator = new Thread(() -> { while (true) { dataHolder.operateData(); } }); operator.start(); Thread checker = new Thread(() -> { while (true) { dataHolder.check(); } }); checker.start(); }}
加上 volatile
关键字
volatile long e;
就不存在所谓的指令重排
volatile 变量只能确保可见性。
/** * @author: 毛利 */public class Test {// public static final int num = 1; private /*volatile*/ boolean flag = true;// private volatile boolean flag = true; public void testvolatile(){ System.out.println("开始读取……"); while (flag){// System.out.println(num); } System.out.println("读取完成……"); } public static void main(String[] args) { Test test = new Test(); new Thread(test::testvolatile).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } test.flag = false; }}
一直卡死在哪里
如果加上了 volatile,那么 flag 就是可见了
如果可以读取静态变量 num,flag 也可以可见了
果然读取完内存,然后读取完成
参考: https://www.jianshu.com/p/15106e9c4bf3 https://www.cnblogs.com/laowen-zjw/p/6735790.html