volatile三大特性
1.对volatile的理解
1.volatile是Java虚拟机提供的轻量级的同步机制(同步:synchronized)乞丐版的synchronized。
有三大特性:保证可见性
不保证原子性
禁止指令重排
可见性:
多个线程,访问java主内存中的同一对象,获取对象之后,各自都拷贝到自己的线程内存中,当有一个线程中的对象改变时,需要写回给主内存中,主内存就会通知其他线程此对象改变了,需要重新拷贝的过程就叫可见性
未加volatile关键字
代码演示
package com.hzy;
import java.util.concurrent.TimeUnit;
class MyData{
int number = 0;
public void addT060(){
this.number = 60;
}
}
/**
* 验证线程的可见性
* 1.假如int number = 0 ;number变量之前根本没有添加volatile关键字,没有可见性
*/
public class VolatileDemo {
public static void main(String[] args) {
//资源类
MyData myData = new MyData();
//第一个线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in ");
//暂停一会线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改内存中数据为60
myData.addT060();
System.out.println(Thread.currentThread().getName()+"\t update number value : "+ myData.number);
},"AAA").start();
//第二个线程
while (myData.number == 0){
//如果number == 0 mian线程就会在这里一直循环,知道number的值不在等于0
}
//如果number中的数据不再等于0,就会执行到下面代码
System.out.println(Thread.currentThread().getName()+"\t mission is over");
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7pvxfvLu-1614656364824)(C:\Users\HS\AppData\Roaming\Typora\typora-user-images\image-20210228125951266.png)]
加入volatile关键字
代码演示
package com.hzy;
import java.util.concurrent.TimeUnit;
class MyData{
volatile int number = 0;
public void addT060(){
this.number = 60;
}
}
/**
* 验证线程的可见性
* 1.假如int number = 0 ;number变量之前根本没有添加volatile关键字,没有可见性
*/
public class VolatileDemo {
public static void main(String[] args) {
//资源类
MyData myData = new MyData();
//第一个线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in ");
//暂停一会线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改内存中数据为60
myData.addT060();
System.out.println(Thread.currentThread().getName()+"\t update number value : "+ myData.number);
},"AAA").start();
//第二个线程
while (myData.number == 0){
//如果number == 0 mian线程就会在这里一直循环,知道number的值不在等于0
}
//如果number中的数据不再等于0,就会执行到下面代码
System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number value: " + myData.number);
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BaavQ7w6-1614656364827)(C:\Users\HS\AppData\Roaming\Typora\typora-user-images\image-20210228130305136.png)]
不保证原子性:
为什么没有保证原子性:
在写回主内存中时,多个线程写修改了线程内部变量值后,写回主内存,发生了重复写值现象,没有区分前后顺序,造成了原子性缺失
代码演示
package com.hzy;
import java.util.concurrent.TimeUnit;
class MyData1{
volatile int number = 0;
public void addPlusPlus(){
number++;
}
}
/**
* 验证线程的不保证原子性
* 1.原子性:完整性,不可分割,即某个先后才能正在做某个具体业务时,中间不可加塞或者不可分割,需要整体完整
* 要么同时成功,要么同时失败
*/
public class VolatileDemo2 {
public static void main(String[] args) {
MyData1 myData1 = new MyData1();
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
myData1.addPlusPlus();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值
while(Thread.activeCount() > 2){
//Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。
Thread.yield();
}
//mian线程的最终值
System.out.println(Thread.currentThread().getName() + "\t finally number value :" + myData1.number);
}
}
如果保持了原子性,最终值应该是20000,实际每次得到的值都不相同
如果需要保证原子性,代码如下,在计算方法上加sychronized关键字
package com.hzy;
import java.util.concurrent.TimeUnit;
class MyData1{
volatile int number = 0;
public synchronized void addPlusPlus(){
number++;
}
}
/**
* 验证线程的不保证原子性
* 1.原子性:完整性,不可分割,即某个先后才能正在做某个具体业务时,中间不可加塞或者不可分割,需要整体完整
* 要么同时成功,要么同时失败
*/
public class VolatileDemo2 {
public static void main(String[] args) {
MyData1 myData1 = new MyData1();
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
myData1.addPlusPlus();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值
while(Thread.activeCount() > 2){
//Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。
Thread.yield();
}
//mian线程的最终值
System.out.println(Thread.currentThread().getName() + "\t finally number value :" + myData1.number);
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toL58O0B-1614656364830)(C:\Users\HS\AppData\Roaming\Typora\typora-user-images\image-20210228132625648.png)]
解决不保证原子性问题:
1,在执行运算的方法中加syhronized关键字
2.使用JUC下的atomic,
代码示例:
package com.hzy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData1 {
//AtomicInteger 默认值为 0
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtomic(){
//源码中:加1
atomicInteger.getAndIncrement();
}
}
/**
* 验证线程的不保证原子性
* 1.原子性:完整性,不可分割,即某个先后才能正在做某个具体业务时,中间不可加塞或者不可分割,需要整体完整
* 要么同时成功,要么同时失败
*/
public class VolatileDemo2 {
public static void main(String[] args) {
MyData1 myData1 = new MyData1();
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
myData1.addAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值
while(Thread.activeCount() > 2){
//Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。
Thread.yield();
}
//mian线程的最终值
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger finally number value :" + myData1.atomicInteger);
}
}
JMM(java内存模型)一种抽象的规范或规则,并不真正存在
可见性,原子性,有序性
禁止指令重排:
在代码执行时,是编译为字节码指令来提供给进程来执行,在多线程环境下,线程抢到的执行顺序可能会有所不同,会造成数据混乱问题,这个时候数据的安全性机会收到威胁,所以就需要禁止指令重排。保证了多线程环境下不会出现乱序执行的现象
使用:在执行的变量前加volatile
禁止指令重排的好处如下,代码示例:
单线程-单例模式
package com.audition.volatiles;
/**
* Created with IntelliJ IDEA.
*
* @Auther: 两杯水
* @Date: 2021/02/28/17:15
* @Description: 单线程-单例模式
*/
public class VolatileDemo3 {
private static VolatileDemo3 instance= null;
private VolatileDemo3(){
System.out.println(Thread.currentThread().getName() +"\t 我是构造方法 ");
}
public static VolatileDemo3 getInstance(){
if(instance == null){
instance = new VolatileDemo3();
}
return instance;
}
public static void main(String[] args) {
//main线程的操作动作
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
}
}
执行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eMHAx9mv-1614656364834)(C:\Users\86178\AppData\Roaming\Typora\typora-user-images\image-20210228172728840.png)]
进行多线程时,就会有问题,代码如下:
package com.audition.volatiles;
/**
* Created with IntelliJ IDEA.
*
* @Auther: 两杯水
* @Date: 2021/02/28/17:15
* @Description: 单线程-单例模式
*/
public class VolatileDemo3 {
private static VolatileDemo3 instance= null;
private VolatileDemo3(){
System.out.println(Thread.currentThread().getName() +"\t 我是构造方法 ");
}
public static VolatileDemo3 getInstance(){
if(instance == null){
instance = new VolatileDemo3();
}
return instance;
}
public static void main(String[] args) {
/* //main线程的操作动作
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());*/
//多线程
for (int i = 0; i <= 10; i++) {
new Thread(()->{
VolatileDemo3.getInstance();
},String.valueOf(i)).start();
}
}
}
执行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MhYAi8QI-1614656364836)(C:\Users\86178\AppData\Roaming\Typora\typora-user-images\image-20210228172815095.png)]
得到结果:多线程下的单例模式,由于多个线程执行的时间不同,导致了构造方法多次被创建实例,也就破坏了原来的单例思想
解决方法
1.在执行方法上加synchronized关键字:
package com.audition.volatiles;
/**
* Created with IntelliJ IDEA.
*
* @Auther: 两杯水
* @Date: 2021/02/28/17:15
* @Description: 单线程-单例模式
*/
public class VolatileDemo3 {
private static VolatileDemo3 instance= null;
private VolatileDemo3(){
System.out.println(Thread.currentThread().getName() +"\t 我是构造方法 ");
}
public static synchronized VolatileDemo3 getInstance(){
if(instance == null){
instance = new VolatileDemo3();
}
return instance;
}
public static void main(String[] args) {
/* //main线程的操作动作
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());*/
//多线程
for (int i = 0; i <= 10; i++) {
new Thread(()->{
VolatileDemo3.getInstance();
},String.valueOf(i)).start();
}
}
}
但是synchronized属于重量级锁,此加锁方式会造成性能降低
2.使用DCL(Double check Lock 双端检索机制)
但是由于指令重排序的原因,在线程很多并发的情况下,此方法也会导致对象重复获取,所以需要对此对象加上volatile