1、volatile是Java虚拟机提供的轻量级的同步(synchronized)机制
1.1、保证可见性
1.2、不保证原子性
1.3、禁止指令重排(有序性)
2、JMM你谈谈
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
1、线程解锁前,必须把共享变量的值刷新回主内存
2、线程加锁前,必须读取主内存的最新值到自己的工作内存
3、加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存就是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
例子:一个student对象同时被t1、t2、t3线程同时访问,修改其中的age,每个线程都会从主内存中拷贝一份age到自己的工作内存,假设t1将拷贝的age=25修改为37,JMM会立即将修改后的变量通知给主内存,让t2、t3线程都能拿到最新的数据,这就是JMM的保证可见性。
JMM的特性:
2.1、可见性
2.2、原子性
2.3、有序性
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData {
volatile int number = 0;
public void addTo60() {
this.number = 60;
}
//注意:此时number前面是加了volatile关键字修饰的,volatile不保证原子性
public void addPlusPlus() {
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtomic() {
atomicInteger.getAndIncrement();
}
}
/**
* 1 验证volatile的可见性
* 1.1 假如int number = 0;,number变量之前根本没有加volatile关键字修饰,没有可见性
* 1.2添加了volatile,可以解决可见性问题
*
* 2 验证volatile不保证原子性
* 2.1 原子性指的是什么意思?
* 不可分隔,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整,要么同时成功,要么同时失败。
* 2.2 如何解决原子性
* 1 加synchronized
* 2 使用JUC下面的AtomicInteger
*/
public class VolatileDemo {
public static void main(String[] args) {
//seeOkByVolatile();
MyData myData = new MyData();
for(int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myData.addPlusPlus();
myData.addAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果
//为什么不用countdown,semaphore
while(Thread.activeCount() > 2) {//这里大于2,是吧main线程和GC线程排除掉了
Thread.yield();//yield让出当前CPU时间片重新竞争CPU时间片的做法不严谨
}
//期望值是20000,多线程容易出现写丢失的情况
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t int type finally number value:" + myData.number);
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t AtomicInteger type finally number value:" + myData.atomicInteger);
}
//volatile可以保证可见性,及时通知其他线程主物理内存的值已经被修改
private static void seeOkByVolatile() {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\tcome in");
//暂停一会线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(new Date() + "\t" +Thread.currentThread().getName() + "\tupdate number value:" + myData.number);
},"AAA").start();
//Thread.sleep(5000);//自己发挥,采用睡眠方式让上面的AAA线程线程行,也可以避免下面while的死循环
System.out.println(myData.number);
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + myData.number);
//第二个线程就是main线程
while(myData.number == 0) {
//main线程就一直在这里等待循环,直到number值不再等于0
}
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\tmission is over" + myData.number);
}
}
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重牌,一般分为以下3种:
源代码==》编译器优化的重排===》指令并行的重排===》内存系统的重排===》最终执行的指令
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑指令之间的数据依赖性。
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
例子:多线程情况下会指令重排,method02中的输出有可能是5,也有可能是6,解决方案:使用volatile修饰变量,禁止指令重排
volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。
内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:
一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。
由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指定重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
你在哪些地方用到过volatile?
单例模式DCL代码
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo()");
}
//在方法上使用synchronized太重,推荐使用DCL(double check lock双端检索机制)
public static SingletonDemo getInstance() {
if(instance == null) {
synchronized(SingletonDemo.class) {
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());
for (int i = 1; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}