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

git使用记录九:开发中临时加塞了紧急任务怎么处理

北京上海开车遇加塞,像个人行不行?!

kubernetes 二进制安装(v1.20.15)加塞一个工作节点

代码注释的艺术——优秀代码真的不需要注释吗?

代码注释的艺术——优秀代码真的不需要注释吗?

代码注释的艺术——优秀代码真的不需要注释吗?