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详解的主要内容,如果未能解决你的问题,请参考以下文章

(转) Java中的负数及基本类型的转型详解

详解Android WebView加载html片段

14.VisualVM使用详解15.VisualVM堆查看器使用的内存不足19.class文件--文件结构--魔数20.文件结构--常量池21.文件结构访问标志(2个字节)22.类加载机制概(代码片段

Python中verbaim标签使用详解

Yii2片段缓存详解

DOM探索之基础详解——学习笔记