作者:毕来生
微信:878799579
- 什么是JUC?
JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类
2.Volatile关键字
1、如果一个变量被volatile关键字修饰,那么这个变量对所有线程都是可见的。
2、如果某条线程修改了被Volatile修饰的这个变量值,修改后的值对于其他线程来时是立即可见的。
3、并不是经过Volatile修饰过的变量在多线程下就是安全的
4、多线程间可以使用SynchronousQueue或者Exchanger进行数据之间传递
3.内存可见性
内存可见性(Memory Visibility)是指当某个线程正在使用对象状态 而另一个线程在同时修改该状态,需要确保当一个线程修改了对象 状态后,其他线程能够看到发生的状态变化。
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
原理同CAS原理相同,不懂的同学可以自行百度,附上一张CAS演示图供大家参考
4.实战举例
通过线程来修改变量count的值,使用Volatile关键字修饰和不使用Volatile修饰count变量结果对比。
首先我们来看一下通过内部类实现Runnable,变量使用Volatile关键字修饰演示以及结果
package org.bilaisheng.juc;
/**
* @Author: bilaisheng
* @Wechat: 878799579
* @Date: 2019/1/1 16:29
* @Todo: 通过内部类实现Runnable,变量使用Volatile关键字修饰演示
* @Version : JDK11 , IDEA2018
*/
public class NoVolatileTest{
public static void main(String[] args) {
NoVolatileThread noVolatileThread = new NoVolatileThread();
new Thread(noVolatileThread).start();
while (true){
if(noVolatileThread.isFlag()){
System.out.println("flag 此时为true !");
break;
}
}
}
}
class NoVolatileThread implements Runnable{
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(Thread.currentThread().getName() + " flag = " + flag);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
运行结果如下图所示:
接下来我们来看一下通过内部类实现Runnable,变量不使用Volatile关键字修饰演示以及结果
package org.bilaisheng.juc;
/**
* @Author: bilaisheng
* @Wechat: 878799579
* @Date: 2019/1/1 16:53
* @Todo: 通过内部类实现Runnable,变量使用Volatile关键字修饰演示
* @Version : JDK11 , IDEA2018
*/
public class VolatileTest{
public static void main(String[] args) {
VolatileThread volatileThread = new VolatileThread();
new Thread(volatileThread).start();
while (true){
// if的判断volatile保证当时确实正确,然后线程a可能处于休眠状态,
// 线程b也判断不存在,b线程就new了一个。
// 然后a线程wake up,据需执行new volatile获取最新值。
if(volatileThread.isFlag()){
System.out.println("flag 此时为true !");
break;
}
}
}
}
class VolatileThread implements Runnable{
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(Thread.currentThread().getName() + " flag = " + flag);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
运行结果如下图所示:
通过对比我们发现在通过Volatile修饰和不通过Volatile修饰的变量,输出结果竟然会有些偏差。到底是为什么呢?
我们逐步拆解上面代码执行步骤:
1、针对于不使用Volatile关键字修饰变量:
- 步骤一:默认flag = false;
- 步骤二main线程的缓存区域没有刷新 flag的值。所以flag 还是false。故没有输出<flag 此时为true !>
- 步骤三:子线程输出 Thread-0 flag = true
2、针对于使用Volatile关键字修饰变量:
- 步骤一:默认flag = false;
- 步骤二:主线程看到flag是被Volatile关键字修饰的变量。则获取最新的flag变量值,此时flag = true。故输出<flag 此时为true !>
- 步骤三:子线程输出 Thread-0 flag = true
- Volatile的优点
可见性:被Volatile修饰的变量可以马上刷新主内存中的值,保证其他线程在获取时可以获取最新值,所有线程看到该变量的值均相同。
轻量级的synchronized,高并发下保证变量的可见性。
6.Volatile的缺点
1、频繁刷新主内存中变量,可能会造成性能瓶颈
2、不具备操作的原子性,不适合在对该变量的写操作依赖于变量本身自己。例如i++,并不能通过volatile来保证原子性