会编程的羽流云
Java JUC——volatile关键字
一、摘要
接下来的一周时间我将开始学习一些简单的并发编程的知识,做一些记录以便以后研习拓展。
二、Java JUC简介
在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。
三、多线程
我们使用多线程主要是为了提高效率,换句话说尽可能利用cpu的资源或者说尽可能利用系统的资源,但是如果使用的不当,不仅不能提高效率反而性能会更低,因为多线程的开销会比单线程要大,它涉及到了线程之间的创建、销毁、调度以及cpu切换等等一系列操作。
四、例子
1、程序如下
package cn.zachariah.chapter01;
/**
* @ClassName TestVolatile
* @Description TODO valatile 关键学习
* @Author zachariah
* @Date 2021/1/7 23:17
*/
public class TestVolatile {
public static void main(String[] args) {
Thread1 td1 = new Thread1();
Thread2 td2 = new Thread2(td1);
td1.start();
td2.start();
}
}
class Thread1 extends Thread{
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
flag = true;
System.out.println("当前flag值为"+ flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
class Thread2 extends Thread{
Thread1 td;
public Thread2(Thread1 td) {
this.td = td;
}
@Override
public void run() {
while (true){
if (td.isFlag()){
System.out.println("当前flag值为:"+td.isFlag()+",Thread2结束运行");
break;
}
}
}
}
2、运行结果
当前flag值为true
注意上述程序并未停止运行
3、原因
为什么会产生这样的结果呢!我们分析一下原因:
1) 首先Thread1在被JVM加载时会在内存中写入一个值为false的flag变量2) 当类加载完成后程序开始运行,td1与td2启动,首先td1和td2会将主存中的flag变量读取到自己的缓存中去进行相应的处理
3) td1做的处理是将flag的值修改为true,td2是判断flag的值
4) td1完成操作之后将主存中的flag修改为true,但是由于td2由于while(true)的原因无法再从主存中同步flag的值
5) 所以td2依然使用自己缓存中的flase再执行,因此导致程序无法结束
4、结论
综上所述这个出现的根本原因就是内存可见性的问题,解决方法有两个第一个就是使用volatile关键字修饰flag变量实现变量在内存中是可见的,也可以通过加锁的方式使td2在while
(true)的过程中依然可以同步主存中的flag值
1)修改Thread2,加synchronized锁:
while (true){
synchronized (td){
if (td.isFlag()){
System.out.println("当前flag值为:"+td.isFlag()+",Thread2结束运行");
break;
}
}
}
2)修改Thread1,将flag使用volatile关键字修饰:
private volatile boolean flag = false;
五、volatile关键字
1、volatile关键字作用
从上面的例子我们可以看出volatile的主要作用就是当多个线程操作共享数据时可以保证内存中的数据是可见的,通俗的将就是它可以确保变量的更新操作可以通知到其他线程,他相较于synchronized而言是更轻量级的同步策略。
2、如何理解
如何理解volatile变量呢!我们可以将volatile变量的读和写操作类比成加了锁的get和set方法,只不过在访问volatile变量是不会执行加锁操作,因此也不会使线程发生阻塞。
3、注意事项
注意:虽然volatile变量很方便,但是也存在局限性,因为volatile变量只保证了原子性,它不同于加锁机制既可以保证可见性也可以保证原子性。volatile变量通常用作某种操作完成、发生中断或者某种状态的标识。尽管volatile变量也可以用于表示其他的状态信息,但是使用时也要非常小心。例如volatile的语义不足以确保递增操作(count++)的原子性,除非你能确保只有一个线程在执行对变量的写操作。
4、使用条件
所以当且仅当满足以下所有条件时,才应该使用volatile变量:
1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。2、该变量不会与其他状态变量一起纳入不变性条件中。
3、在访问变量是不需要加锁。