volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))

volatile是java虚拟机提供的轻量级的同步机制:

1.保证可见性:线程之间可见性(及时通知)
2.不保证原子性
3.禁止指令重排

先了解一下jvm同步
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(或者称为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的栈空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
在这里插入图片描述

一、volatile的可见性demo验证

一、没有加volatile

package Volatile;import java.util.concurrent.TimeUnit;/*** volatile的可见性* demo*/
class MyData{int number = 0;public void addTO60(){this.number = 60;}
}public class demo {public static void main(String[] args) {MyData myData = new MyData();new Thread(() -> {System.out.println(Thread.currentThread().getName() + "come in");//让线程等待3stry {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTO60();System.out.println(Thread.currentThread().getName() + "updated number value:" + myData.number);},"AAA").start();//第二个线程是main线程while(myData.number == 0){//循环等待}System.out.println(Thread.currentThread().getName());}
}

运行结果可以看出来会卡在while循环处
在这里插入图片描述
二、加上volatile后

/*** volatile的可见性* demo*/
class MyData{volatile int number = 0;public void addTO60(){this.number = 60;}
}public class demo {public static void main(String[] args) {MyData myData = new MyData();new Thread(() -> {System.out.println(Thread.currentThread().getName() + "come in");//让线程等待3stry {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTO60();System.out.println(Thread.currentThread().getName() + "updated number value:" + myData.number);},"AAA").start();//第二个线程是main线程while(myData.number == 0){//循环等待}System.out.println(Thread.currentThread().getName()+"mission is over;main get number value:"+myData.number);}
}

结果:
在这里插入图片描述

结果可以看出,当其他线程修改了主内存空间的值时,加上了volatile主内存空间的值改变后会及时通知其他线程主物理内存的值被修改。

二:不保证原子性demo验证

下面代码:20个线程,每个线程进行1000次number++,理论上结果是两万,实际运行:

    public static void main(String[] args) {MyData myData = new MyData();for (int i = 1; i <= 20; i++) {new Thread(() -> {for (int j = 0; j <1000 ; j++) {myData.addPlusPlus();}},String.valueOf(i)).start();}while(Thread.activeCount()>2){Thread.yield();}System.out.println(myData.number);}

结果并不是两万:所以说不能保证原子性,不能保证结果一致性,存在线程安全问题
在这里插入图片描述

解决办法:

1.synchronized(有点小题大做)
synchronized public void addPlusPlus() {this.number++;
}
2.使用AtomicInteger
class MyData {volatile int number = 0;public void addTO60() {this.number = 60;}public void addPlusPlus() {this.number++;}AtomicInteger atomicInteger = new AtomicInteger();public void atomicPlusPlus(){atomicInteger.getAndIncrement();}
}
public class demo {public static void main(String[] args) {MyData myData = new MyData();for (int i = 1; i <= 20; i++) {new Thread(() -> {for (int j = 0; j <1000 ; j++) {myData.addPlusPlus();myData.atomicPlusPlus();}},String.valueOf(i)).start();}while(Thread.activeCount()>2){Thread.yield();}System.out.println(myData.number);System.out.println(myData.atomicInteger);}

就是用AtomicInteger来代替number,用getAndIncrement来代替number++(Atomic相关内容可以看API了解方法怎么用。)就可以保证原子性。

三、禁止指令重排

理解这里需要了解一点编译器,编译器在编译时,会有个优化指令重排,在多线程下指令重排会造成线程安全问题,最终一致性无法保证

多线程环境中线程的交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

例:单例模式为例(在多线程下,普通的单例模式并不适用)

class SingletonDemo{private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"调用构造方法SingletonDemo");}public static SingletonDemo getInstance(){if (instance == null){instance = new SingletonDemo();}return instance;}
}
public class demo1 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}}
}

结果:可以看到并不是只会创建一个对象
在这里插入图片描述

使用dlc(Double Check Lock双端检索机制),代码如下

class SingletonDemo{private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"调用构造方法SingletonDemo");}//    public static SingletonDemo getInstance(){
//        if (instance == null){
//            instance = new SingletonDemo();
//        }
//        return instance;
//    }//使用dlc双端检索机制public static SingletonDemo getInstance(){if (instance == null){synchronized(SingletonDemo.class){if (instance == null){instance = new SingletonDemo();}}}return instance;}
}
public class demo1 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}}
}

结果:
在这里插入图片描述

那么这样就可以了吗?

不行,因为编译器会进行指令重排,instance = new SingletonDemo();编译器会拆分成三步,
1.memory = allocate(); //分配对象内存空间
2.instance(memory);//初始化对象
3.instance = memory; //设置instance指向刚分配的内存地址,此时instace!=null

所以可能出现,另一个线程进入了第一个if (instance == null){时,instance的引用对象还未初始化完成,所以要加入volatile来禁止指令重排

package Volatile;class SingletonDemo{private static volatile SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"调用构造方法SingletonDemo");}//    public static SingletonDemo getInstance(){
//        if (instance == null){
//            instance = new SingletonDemo();
//        }
//        return instance;
//    }//使用dlc双端检索机制public static SingletonDemo getInstance(){if (instance == null){synchronized(SingletonDemo.class){if (instance == null){instance = new SingletonDemo();}}}return instance;}
}
public class demo1 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/437577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CAS(比较并交换)学习CAS实现原子性+volatile实现可见性,cas与synchronized比较的优缺点

1、CAS底层原理&#xff1f; 自旋锁(cas思想)unsafe类&#xff0c;保证原子性靠的是unsafe类 1.首先可以看到&#xff1a; atomicInteger.getAndIncrement();getAndIncrement()的底层是 public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset,…

cas引出的ABA问题?如何解决?- 理解原子引用、时间戳(版本号)原子引用

ABA问题&#xff1a; 假如有两个线程1&#xff0c;2&#xff1b; cas下&#xff1a;1.线程取值完等待&#xff0c;2线程取值并把A改成B&#xff0c;有把B改成A&#xff0c;这是1线程执行会任务A还是原来的A没有发生改变&#xff0c;如果不在乎中间结果&#xff0c;只看收尾&am…

ArrayList、HashMap、HashSet是线程不安全的,高并发下如何解决?

1.故障现象 &#xff1a;ArrayList的add()方法并没有使用synchronized所以是线程不安全的&#xff0c;会造成java.util.ConcurrentmodificationException(并发修改异常) 2.导致原因&#xff1a;并发争抢修改导致 3.解决办法&#xff08;以ArrayList为例&#xff0c;其他类似&…

java -锁(公平、非公平锁、可重入锁【递归锁】、自旋锁)

1.公平锁、非公平锁 2.可重入锁&#xff08;递归锁&#xff09; 3.自旋锁 AtomicReference atomicReference new AtomicReference();//原子引用线程下面代码5秒钟自旋了10万次&#xff0c;还是很消耗CPU的 package HighConcurrency;import java.util.concurrent.TimeUnit; imp…

CountDownLatch,同步辅助类

public class CountDownLatchextends Object一个同步辅助类&#xff0c;在完成一组正在其他线程中执行的操作之前&#xff0c;它允许一个或多个线程一直等待。 假设定义一个计数器为 5。每个线程完成任务后计数减1计数器减为 0 则通知等待的线程。 用给定的计数 初始化 Count…

CyclicBarrier-同步辅助类

与CountDownLatch相反&#xff0c;CyclicBarrier是做加法 public class CyclicBarrierextends Object一个同步辅助类&#xff0c;它允许一组线程互相等待&#xff0c;直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中&#xff0c;这些线程必…

阻塞队列-BlockningQueue

阻塞队列的前言介绍引用于https://www.cnblogs.com/aspirant/p/8657801.html&#xff0c;写的挺好有兴趣可以看一看 一. 前言 在新增的Concurrent包中&#xff0c;BlockingQueue很好的解决了多线程中&#xff0c;如何高效安全“传输”数据的问题。通过这些高效并且线程安全的…

多线程下的生产者消费者(一个初始值为0的变量,两个线程一个加1一个减1,轮询5轮)

在使用Lock之前&#xff0c;我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法&#xff0c;与Lock配合可以实现等待/通知模式 下面用的是lock锁 …

synchronized和ReentrantLock区别,用新的lock有什么好处?举例说说

原始构成&#xff1a;synchronized是JVM层面的&#xff0c;底层通过monitorenter和monitorexit来实现的。Lock是JDK API层面的。&#xff08;synchronized一个enter会有两个exit&#xff0c;一个是正常退出&#xff0c;一个是异常退出&#xff08;保证肯定可以退出&#xff09;…

阻塞队列,来写生产者消费者,生产一个消费一个

用阻塞队列来写&#xff0c;就不需要程序员来关心什么时候阻塞线程await&#xff0c;什么时候唤醒线程notify&#xff08;&#xff09;。 类似消息中间件 package JUC;import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import…

Callable接口-创建线程的第三种方法

Callable和Runable相比 //Runnable和Callable区别 //1.Callable带返回值 //2.run()不会抛异常&#xff0c;call()会抛异常 //3.实现的接口不同 package JUC;import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concur…

线程池ThreadPool,线程池底层ThreadPoolExecutor方法七大参数,拒绝策略,以及实际开发中高并发下用到哪个线程池?

为什么要用线程池 基本的三个线程池的底层就是ThreadPoolExecutor类 ExecutorService threadPool Executors.newFixedThreadPool(int);//固定线程数的线程池&#xff0c;&#xff0c;执行长期的任务&#xff0c;性能好 ExecutorService threadPool Executors.newSingleThrea…

【转】2.1.1 购买阿里云服务器(Ubuntu系统)

购买阿里云服务器 优惠购买链接 戳我购买 Cpu和内存 M2对服务器配置要求比较高&#xff0c; cpu要大于等于1, 内存要大于等于2 选择Ubuntu镜像 最好用官方提供的公共镜像&#xff0c;不要用第三方的。 其他的配置 其他的配置按你自己的需求来选择。 控制台 购买完成后…

死锁编码及定位分析(故障排查)

什么是死锁&#xff1f; 在Java中使用多线程&#xff0c;就会有可能导致死锁问题。死锁会让程序一直卡住&#xff0c;不再程序往下执行。我们只能通过中止并重启的方式来让程序重新执行。 造成死锁的原因&#xff1a; 当前线程拥有其他线程需要的资源当前线程等待其他线程已…

【转】在win10接双显示器(扩展模式)怎么把鼠标游标切换到第二台显示器上?

作者&#xff1a;朱宁 链接&#xff1a;https://www.zhihu.com/question/50002939/answer/120465673 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 使用软件 DisplayFusion Pro 这个软件体量比较大&#xff0c;蓝…

JAVA的Future类

学习callable中用到了Future&#xff0c;找了个博客&#xff0c;可以看下 https://blog.csdn.net/u014209205/article/details/80598209

java单例模式(饿汉式和懒汉式的几种不同写法)

1.饿汉式&#xff08;直接创建&#xff09;三种创建方法代码里面都有 第三种静态代码块方式&#xff0c;需要创建个properties文件 package java2;import java.io.IOException; import java.util.Properties;/*** 单例模式设计步骤&#xff1a;* 1.构造器私有化* 2.内部创…

【转】一键将Web应用发布到云-Azure Web App!

我们现在越来越多的传统应用&#xff0c;逐步向云端迁移&#xff0c;原先私有云的部署模式&#xff0c;逐步向云端PaaS IaaS转变。例如&#xff1a; 我们在云端Azure中申请VM虚拟机&#xff0c;将我们的Web应用部署到VM的IIS中&#xff0c;同时做云服务的高可用性集和NLB&…

【转】微软Azure Functions使用入门

微软公司于近期发布了Azure Functions以支持AWS Lambda。本文将介绍如何开始使用这个事件驱动服务&#xff0c;以及这项服务是否适合您。 无论是超大规模云供应商、软件开放人员还是企业管理员&#xff0c;目前最热门的技术之一就是无服务器计算。也就是所谓的功能即服务&…

【转】The underlying connection was closed

转自: https://support.microsoft.com/en-us/kb/915599 SUMMARY This article describes error messages that you may receive when you use the Microsoft .NET Framework 1.1 Service Pack 1 (SP1) to make HTTP requests or when you call a Web service. SYMPTOMS Whe…