1.谈谈你对volatile的理解
volatile是java虚拟机提供的轻量级的同步机制
有三大特性:
保证可见性,线程休修改数据后其他线程可以及时感知
不保证原子性
JMM模型 :java线程 *** 作内存中数据的过程需要各个线程从主内存将数据拷贝到自己的工作内存, *** 作完毕后再写入主内存,主内存中的数据各个线程之间是共享的,但是线程间是不能互相访问对方的工作内存中的数据的。
需要保证可见性 :线程间及时通信
不原子性:原子性 *** 作
, 禁止指令重排:指令代码有序性 才可以保证线程安全
代码可以验证 MyData中num没有添加volatile关键字
线程 *** 作原子 原子类Mydata
package com.volalite; class MyData{ //int num = 0; volatile int num = 0; public void addTo26(){ this.num=26; } public void add1(){ num++; } }
可见性验证
package com.volalite; import java.util.concurrent.TimeUnit; public class VolatileDome { public static void main(String[] args) { MyData myData = new MyData(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"t进来了"); try { TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.printStackTrace(); } myData.addTo26(); System.out.println(Thread.currentThread().getName()+"感知的num值是:"+myData.num); },"线程1").start(); System.out.println(Thread.currentThread().getName()+"感知的num值是:"+myData.num); //如果mian线程无法感知num的变化,将一直阻塞, while (myData.num==0){ } System.out.println(Thread.currentThread().getName()+"感知到线程间数据 *** 作"); } }
mian线程一直认为num==0,一直阻塞
MyData中num添加volatile关键字后
可以看到volatile使得线程间数据 *** 作可以互相感知
不保证原子性验证
package com.volalite; public class VolatileDemo2 { public static void main(String[] args) { MyData myData = new MyData(); for (int i = 0; i < 100; i++) { new Thread(()->{ for (int j = 0; j < 200; j++) { myData.add1(); } }).start(); } while (Thread.activeCount()>2){ Thread.yield();//运行线程大于2 说明10个循环线程还没运行完毕,在此等待将10个线程运行完毕,主线程不载阻塞 } System.out.println(myData.num); } }
结果
原子性问题解决
用AtomicInteger代替int
package com.volalite; import java.util.concurrent.atomic.AtomicInteger; class MyData{ //int num = 0; AtomicInteger num = new AtomicInteger(0); public void add1(){ num.incrementAndGet(); } }
jvm指令重排案例1
package com.volalite; import java.util.concurrent.TimeUnit; public class VisibilityTest extends Thread { private boolean stop; @Override public void run() { int i = 0; while (!stop) { i++; } System.out.println("finish loop,i=" + i); } public void stopIt() { stop = true; } public boolean getStop() { return stop; } public static void main(String[] args) throws Exception { VisibilityTest v = new VisibilityTest(); v.start(); Thread.sleep(1000); v.stopIt(); Thread.sleep(2000); System.out.println("finish main"); System.out.println(v.getStop()); } }
jvm指令重排案例2
package com.volalite; import java.util.concurrent.TimeUnit; public class ReSortDemo { volatile int a =0; boolean flag =false; public void methods1(){ a=1; flag =true; } public void methods2(){ if(flag){ a=a+5; System.out.println("methods2结果是:"+a); } } public static void main(String[] args) { ReSortDemo reSortDemo = new ReSortDemo(); new Thread(()->{ reSortDemo.methods1(); reSortDemo.methods2(); }).start(); new Thread(()->{ reSortDemo.methods2(); reSortDemo.methods1(); }).start(); try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(reSortDemo.a); } }
如果指令重排存在,案例1应该存在程序阻塞的情况,案例2应该输出不应该是
methods2结果是:6
methods2结果是:11
不知道是编译器的原因还是其他原因,重拍现象没有出现。
单例模式的代码
package com.volalite; public class SingleTonDemo { private static SingleTonDemo instance = null; public SingleTonDemo() { System.out.println("SingleTonDemo构造方法"); } public static SingleTonDemo getInstance(){ if(instance==null){ instance =new SingleTonDemo(); } return instance; } public static void main(String[] args) { System.out.println(SingleTonDemo.getInstance()==SingleTonDemo.getInstance()); System.out.println(SingleTonDemo.getInstance()==SingleTonDemo.getInstance()); System.out.println(SingleTonDemo.getInstance()==SingleTonDemo.getInstance()); } }
输出三个true, 且“SingleTonDemo构造方法”只输出一次,明只有一个对象被构造
多线程情况下,构造方法执行了不止一次
package com.volalite; public class SingleTonDemo { private static SingleTonDemo instance = null; public SingleTonDemo() { System.out.println("SingleTonDemo构造方法"); } public static SingleTonDemo getInstance(){ if(instance==null){ instance =new SingleTonDemo(); } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ SingleTonDemo.getInstance(); },"Thread"+i).start(); } } }
首先给 getInstance()添加synchronized可以解决问题
双端检索机制不一定安全,因为有指令重排的存在
package com.volalite; public class SingleTonDemo { private static SingleTonDemo instance = null; public SingleTonDemo() { System.out.println("SingleTonDemo构造方法"); } public static synchronized SingleTonDemo getInstance(){ if(instance==null){ synchronized (SingleTonDemo.class){ if(instance==null) { instance = new SingleTonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ SingleTonDemo.getInstance(); },"Thread"+i).start(); } } }
DCL(双端检索不安全的原因),jvm初始化对象的过程
memoty = allocate();//1.分配内存空间
instance(memoty );//2.初始化对象
instance = memoty ;//3.设置instance 指向刚分配的内存地址
步骤2,3不存在依赖关系,多线程情况下可能会发生指令重排,3,2
执行,instance 有指向,但是指向了没有初始化的对象,存在线程安全问题。
给 instance 添加volatile关键字可以解决问题
private static volatile SingleTonDemo instance = null;
单例模式线程安全问题解决:1. getInstance 方法添加synchronized(重锁,不推荐)
2.DCL双端检索加volatile,推荐
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)