volatile
volatile:是jvm提供的一种轻量级的同步机制 它保证了可见性和禁止指令重排序,但不保证原子性。
JMM:java内存模型,是一种抽象概念,是一种规范。
JMM同步规范: 1.解锁前,线程必须将值刷新回主内存。
2.加锁前,线程必须将主内存变量的最新值读到工作内存。
3.加锁解锁是同一把锁。
主内存与工作内存: jvm程序运行的实例是线程,线程被创建时,jvm会为每个线程分配一个属于自己的工作内存空间,是线程私有的区域。而主内存是线程共享的区域。主内存中的变量为线程共享,当多线程在计算时,需要将主内存的变量的副本读到自己的工作内存中,在工作内存中完成计算后,再刷新回主内存。
可见性的作用:多线程对变量进行计算时,会把共享变量的副本读到工作内存中,完成计算。这种方式在单线程的情况下没有问题。但是在多线程的情况下,会出现其中一个线程计算完值后,将值刷新到主内存,但是此时其他线程并不知道共享变量被修改了,故而其他线程还会计算一开始的变量值。就会导致计算出问题。
可见性:在多线程操作同一个共享变量时,其中一个线程对变量进行修改后,对其他线程是立即可见的。
可见性验证
package volatiles;
import java.util.concurrent.TimeUnit;
public class visibility {
/*
* volatile:是jvm提供的一种轻量级的同步机制 它保证了可见性和禁止指令重排序,但不保证原子性。
* JMM:java内存模型,是一种抽象概念,是一种规范。 JMM同步规范: 1.解锁前,线程必须将值刷新回主内存。
* 2.加锁前,线程必须将主内存变量的最新值读到工作内存。
* 3.加锁解锁是同一把锁。
*
* 主内存与工作内存: jvm程序运行的实例是线程,线程被创建时,jvm会为每个线程分配一个属于自己的工作内存空间,是线程私有的区域。而主内存是线程共享的
* 区域。主内存中的变量为线程共享,当多线程在计算时,需要将主内存的变量的副本读到自己的工作内存中,在工作内存中完成计算后,再刷新回
* 主内存。
*
* 可见性的作用:多线程对变量进行计算时,会把共享变量的副本读到工作内存中,完成计算。这种方式在单线程的情况下没有问题。但是在多线程的情况下,会出现其中一个线程
* 计算完值后,将值刷新到主内存,但是此时其他线程并不知道共享变量被修改了,故而其他线程还会计算一开始的变量值。就会导致计算出问题。
*
* 可见性:在多线程操作同一个共享变量时,其中一个线程对变量进行修改后,对其他线程是立即可见的。
* */
/*
* 验证可见性
*
* 让AAA线程去修改变量n,修改后的n为60;
* */
public static void main(String[] args) {
mod mod = new mod();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
mod.edit();
System.out.println(Thread.currentThread().getName()+"\t update number value:"+mod.n);
},"AAA").start();
while (mod.n==0){//如果main线程的n值等于零,说明AAA线程对n值的修改是对main不可见。
}
//AAA线程的值对main不可见,此句不会执行。
System.out.println(Thread.currentThread().getName()+"\t mission is over"+mod.n);
}
}
class mod{
int n = 0;
public void edit(){
this.n = 60;
}
}
没有加volatile关键字。
class mod{
volatile int n = 0;
public void edit(){
this.n = 60;
}
}
原子性
package volatiles;
/*
* 原子性:不可分割的操作,某个线程所做的事情不可以被打断,要么同时成功,要么同时失败。
*
* 验证volatile不保证原子性
* */
public class Atomicity {
//20个线程对num值进行操作,每个线程执行1000次,如果有原子性结果是20000;
public static void main(String[] args) {
MyData myData = new MyData();
for(int i=1; i <= 20 ; i++){
new Thread(()->{
for (int j = 1; j <= 1000 ; j++) {
myData.add();
}
},String.valueOf(i)).start();
}
//当前线程数大于2说明,此时还没算完。2是指后台默认有两个线程,main和守护。
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.num);
}
}
class MyData{
volatile int num = 0;
public void add(){
num++;
}
}
所以说明volatile不保证原子性。
原因:由于线程计算时会把变量副本读到自己的工作内存中进行计算的。如果线程A对变量进行修改后,由于某些原因被挂起,此时线程B并不知道变量被修改了,线程B还会对原来的变量进行修改,并把修改后的值刷新回主内存,此时线程A把自己空间中的变量刷新回主内存,就会产生写丢失现象。
解决方法:
package volatiles;
import java.util.concurrent.atomic.AtomicInteger;
/*
* 原子性:不可分割的操作,某个线程所做的事情不可以被打断,要么同时成功,要么同时失败。
*
* 验证volatile不保证原子性
* */
public class Atomicity {
//20个线程对num值进行操作,每个线程执行1000次,如果有原子性结果是20000;
public static void main(String[] args) {
MyData myData = new MyData();
for(int i=1; i <= 20 ; i++){
new Thread(()->{
for (int j = 1; j <= 1000 ; j++) {
myData.add();
myData.addAtomic();
}
},String.valueOf(i)).start();
}
//当前线程数大于2说明,此时还没算完。2是指后台默认有两个线程,main和守护。
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.num);
System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.atomicInteger);
}
}
class MyData{
volatile int num = 0;
public void add(){
num++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtomic(){
atomicInteger.getAndIncrement();
}
}
指令重排
多线程指令重排存在的问题
package volatiles;
/*
* 指令重排:计算机在执行程序的时候,为了提高性能,会将指令重排。也就是说计算机并不保证程序运行的
* 顺序和代码顺序一致,但是可以保证最终结果和代码顺序执行的结果一致。
* */
public class ReSortReqDemo {
int a = 0;
boolean flag = false;
public void method01(){
a = 1; //语句1
flag = true; //语句2
}
/*
* 在单线程的情况下,发生指令重排不会影响以下结果,结果为6
*
* 在多线程的情况下,会出现以下情况。
* 情况一:线程执行正常,输出6;
* 情况二:编译器会发生指令重排,线程A执行method01时先执行语句2后,还没有等到执行语句1时,
* 线程B就执行了method02方法,导致最终结果为5;
*
* */
public void method02(){
if(flag){
a = a+5;
System.out.println("******"+5);
}
}
}
单例模式下存在的问题
package volatiles;
public 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 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=0; i < 10 ; i++){
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
单线程情况执行的结果:
多线程情况执行的结果:
可以看到,在多线程情况下执行的次数小于10次。
解决方法:
1.可以在方法上加synchronized。(不推荐)
2.DCL(Double Check Lock,双端检锁机制)
单例模式DCL代码
package volatiles;
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
}
//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=0; i < 10 ; i++){
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
但是还是会出现安全,因为计算机会出现指令重排,虽然出问题的概率很小。
DCL机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排。
原因在于某一个线程执行到第一次检测,读到的instance不为null时,instance的引用对象可能没有完成初始化。
需要加volatile禁止指令重排。
package volatiles;
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
}
//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=0; i < 10 ; i++){
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}