一、回顾多线程
多线程目的:尽可能提高CPU(系统)的利用率
多线程问题:如果使用不当,性能会降低,原因:开销比特较大、涉及线程间的调度、CPU的切换、线程间的创建和销毁的问题等
二 、volatile关键字
例1 观察:横线能否打印,while(true)是否能结束的了
package www.wzj.juc;
/*
* 一、volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
* 相较于 synchronized 是一种较为轻量级的同步策略。
*
* 每一个线程都会分配一个独立的缓存,用于提高效率
*
* 缓存:涉及读写数据
* 注意:
* 1. volatile 不具备“互斥性”
* 2. volatile 不能保证变量的“原子性”
*
* 观察:横线能否打印,while(true)是否能结束
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();//开启线程
//主线程
while(true){
if(td.isFlag()){ //读取共享数据:flag
System.out.println("------------------");
break;
}
System.out.println("a");
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;//共享数据(实验:加不加volatile)
@Override
public void run() {
try {
Thread.sleep(100); //为了让实验效果更明显,延迟了200毫秒
} catch (InterruptedException e) {
}
flag = true;//对应线程中写入数据
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
结果:多次运行的过程中,出现结果—-→flag=true,同时程序陷入死循环
程序简要的分析:程序中有两个线程main线程和线程1,flag作为多线程的共享数据
思考:按理说flag=true表示线程1已经完成对共享数据flag已经修改,main线程判断后应该跳出while(true)的死循环才对,为什
么会一直处于死循环的状态(与预判相反)
具体分析:JVM会为每个线程分配”独立的缓存(工作内存)”,用于提高效率。线程1抢占到CPU的执行权,首先从主存中拷
贝“flag=false”到自己的工作内存(缓冲区内),然后改值“flag=true”,由于此缓冲区不具有自动刷新的功能,而此时如果main线
程抢占到CPU的执行权,而线程1还来不及将数据写入到主存中,此时main线程读取到的是”flag=false”,由于主线程的while(true)
调用的是系统底层的资源,执行效率非常高,高到(main线程都没有机会从主存中再获取一次数据),一直反复无限的循环,陷入
死循环。
解决方案1:synchronized关键字进行加锁,特点:每次都会刷新对应线程的缓存写入到主存中
缺点:由于synchronized是同步锁,但是我门知道加锁的效率非常低,一个线程持有一把锁,而另外一个线程想访问
此时会处于阻塞状态,线程被挂起,等待CPU重新分配任务,整个执行过程特别复杂,效率特别低(非常耗时)
解决方案2:volatile 关键字,是一种较为轻量级的同步策略
从对程序执行过程中的分析我们了解到:线程1在向内存中准备写入数据的时候,main线程则从主存中去读取数据,获取到的不
是预期的结果,也即:内存的不可见性,线程之间没有进行通信,线程间不知道彼此缓冲区(工作内存)的实际情况,主存不能及
时更新数据
解决思路:线程1在向主存中写入数据的时候,禁止main线程从主存中读数据
volatile作用:涉及到计算机底层的”内存栅栏“,当一个共享变量被volatile修饰时,它会保证修改的值会立即从缓存中
写入(更新到)到主存,当有其他线程需要读取时,它会去内存中读取新值
可以这样理解为:被volatile修饰的变量,它的操作都是在主存中完成的(直接对内存中的数据进行操作)
volatile 特点
(1)不具有互斥性
互斥性:一个线程持有锁,另一个线程进不来(多个线程不能同时进来)
(2)不能保证变量的原子性
原子性:下一篇介绍