JUC-volatile详解
Posted 玩葫芦的卷心菜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC-volatile详解相关的知识,希望对你有一定的参考价值。
volatile是Java虚拟机提供的轻量级同步机制,有三大特性:保证可见性,不保证原子性,禁止指令重排列
JMM(Java内存模型)
volatile有三大特性:可见性、无原子性、禁止重排序
1、可见性:
主内存共享,每个工作线程会创建一份工作内存,操作共享变量,会进行变量拷贝进工作内存,在工作内存中修改后会写回主内存,但是其他工作线程不知道,所以可见性就是当一个工作线程修改共享变量后写回主内存,由主内存通知其他工作线程,旧值作废,重新拷贝新值
// volatile保证可见性,及时通知其他线程,主内存已被修改
private static void seeOkByVolatile() throws InterruptedException {
MyData myData=new MyData();
// CountDownLatch countDownLatch=new CountDownLatch(1);
Thread t1=new Thread(){
@Override
public void run() {
System.out.println(currentThread().getName()+" come in");
// 暂停一段时间,让别的线程读取到数据
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.update();
System.out.println(currentThread().getName()+" update number:"+myData.number);
// countDownLatch.countDown();
}
};
t1.setName("A");
t1.start();
// countDownLatch.await();
// while(Thread.activeCount()>2){
// Thread.yield();
// }
// t1.join();
// System.out.println(myData.number);
// System.out.println();
// 已经读到myData.number=0了
while(myData.number==0){
}
System.out.println("end");
}
休眠3秒,是为了主线程的myData.number已经成功读取主内存
如果没有可见性就会发现子线程修改number后还是一直循环,main线程并不知道已修改
volatile就可以保证可见性
2、原子性
操作一体,要不全部成功,要不全部失败,中间不能加塞(不能有其他操作影响)或者打断,保证结果正确
MyData myData=new MyData();
CountDownLatch countDownLatch=new CountDownLatch(10);
for(int i=0;i<10;i++){
new Thread(()->{
for(int j=0;j<1000;j++) {
myData.incr();
myData.incrAtomic();
}
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(myData.number);
System.out.println(myData.atomicInteger);
线程进行自增,结果不一致
volatile为什么不保证原子性,是因为线程A和线程B都自增1,B写回了主内存,但是还没通知,A线程可能也写回主内存,造成了写覆盖,写丢失的情况(自增覆盖)
所以保证原子性,就需要原子类或者synchronize(但是太重了没必要,原子类更适合),原子类是通过CAS自旋来保证
3、禁止重排列
编译器会优化指令,对指令进行重排列,但在多线程环境可以会造成结果不一致的问题
其实禁止重排列,是加上了内存屏障,同时会强制刷新内存,保证了可见性
单例模式:如果只生成了一个对象就只会出现一次构造输出
class Single{
private static volatile Single instance=null;
public Single(){
System.out.println("Single构造器");
}
public static Single getInstance(){
if(instance==null){
synchronized (Single.class){
if(instance==null){
instance=new Single();
}
}
// instance=new Single();
}
return instance;
}
}
public class DemoSingle {
public static void main(String[] args) {
for(int i=0;i<10;i++){
new Thread(()->{
Single.getInstance();
}).start();
}
}
}
初始化对象的三步骤:
1、分配内存空间memory
2、初始化对象instance(memory)
3、将instance指向内存空间memory(此时instance!=null)
但是如果指令重排列
1、分配内存空间memory
2、将instance指向内存空间memory(此时instance!=null)
3、初始化对象instance(memory)
第二步可能其他线程检测到instance!=null,调过同步代码块继续生成对象(当然这种情况很好,但是需要保证,加上volatile禁止重排列)
以上是关于JUC-volatile详解的主要内容,如果未能解决你的问题,请参考以下文章
14.VisualVM使用详解15.VisualVM堆查看器使用的内存不足19.class文件--文件结构--魔数20.文件结构--常量池21.文件结构访问标志(2个字节)22.类加载机制概(代码片段