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