线程切换会带来原子性的问题java
int i = 1; // 原子操做 i++; // 非原子操做,从主内存读取 i 到线程工做内存,进行 +1,再把 i 写到主内存。
虽然读取和写入都是原子操做,但合起来就不属于原子操做,咱们又叫这种为“复合操做”。编程
咱们能够用synchronized 或 Lock 来把这个复合操做“变成”原子操做。缓存
例子:多线程
//使用synchronized private synchronized void increase(){ i++; } //使用Lock private int i = 0; Lock mLock = new ReentrantLock(); private void increase() { mLock.lock(); try { i++; } finally{ mLock.unlock(); } }
这样咱们就能够把这个一个方法看作一个总体,一个不可分割的总体。并发
除此以前,咱们还能够用java.util.concurrent.atomic里的原子变量类,能够确保全部对计数器状态访问的操做都是原子的。优化
例子:atom
AtomicInteger mAtomicInteger = new AtomicInteger(0); private void increase(){ mAtomicInteger.incrementAndGet(); }
缓存致使可见性问题线程
int v = 0; // 线程 A 执行 v++; // 线程 B 执行 System.out.print("v=" + v);
即便是在执行完线程里的 i++ 后再执行线程 B,线程 B 的输入结果也会有 2 个种状况,一个是 0 和1。code
由于 i++ 在线程 A(CPU-1)中作完了运算,并无马上更新到主内存当中,而线程B(CPU-2)就去主内存当中读取并打印,此时打印的就是 0。blog
禁用缓存能保证可见性,volatile关键字能够禁用缓存
synchronized和Lock可以保证可见性。
致使有序性的缘由是编译优化
咱们都知道处理器为了拥有更好的运算效率,会自动优化、排序执行咱们写的代码,但会确保执行结果不变。
例子:
int a = 0; // 语句 1 int b = 0; // 语句 2 i++; // 语句 3 b++; // 语句 4
这一段代码的执行顺序颇有可能不是按上面的 一、二、三、4 来依次执行,由于 1 和 2 没有数据依赖,3 和 4 没有数据依赖, 二、一、四、3 这样来执行能够吗?彻底没问题,处理器会自动帮咱们排序。
在单线程看来并无什么问题,但在多线程则很容易出现问题。
再来个例子:
// 线程 1 init(); inited = true; // 线程 2 while(inited){ work(); }
init(); 与 inited = true; 并无数据的依赖,在单线程看来,若是把两句的代码调换好像也不会出现问题。
但此时处于一个多线程的环境,而处理器真的把这两句代码从新排序,那问题就出现了,若线程 1 先执行 inited = true; 此时,init() 并无执行,线程 2 就已经开始调用 work() 方法,此时极可能形成一些奔溃或其余 BUG 的出现。
synchronized和Lock能确保原子性,能让多线程执行代码的时候依次按顺序执行,天然就具备有序性。
而volatile关键字也能够解决这个问题,volatile 关键字能够保证有序性,让处理器不会把这行代码进行优化排序。
**** 码字不易若是对你有帮助请给个关注****
**** 爱技术爱生活 QQ群: 894109590****