Java Volatile應用單例模式實現過程解析
單例模式
回顧一下,單線程下的單例模式代碼
餓漢式
- 構造器私有化
- 自行創(chuàng)建,并且用靜態(tài)變量保存static
- 向外提供這個實例 public
- 強調這是一個單例,用final
public class sington(){
public final static INSTANCE = new singleton();
private singleton(){}
}
第二種:jdk1.5之后用枚舉類型
枚舉類型:表示該類型的對象是有限的幾個
我們可以限定為1個,就稱了單例
public enum Singleto{
INSTANCE
}
第三種靜態(tài)代碼塊
public class Singleton{
public final static INSTANCE;
static{
INSTANCE = new Singleton();
}
private Singleton(){}
}
懶漢式構造器私有化
用一個靜態(tài)變量保存這個唯一實例
提供一個靜態(tài)方法,獲取這個實例
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
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());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
}
}
最后輸出結果:

但是在多線程的環(huán)境下,我們的單例模式是否還是同一個對象了
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) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
從下面的結果我們可以看出,我們通過SingletonDemo.getInstance() 獲取到的對象,并不是同一個,而是被下面幾個線程都進行了創(chuàng)建,那么在多線程環(huán)境下,單例模式如何保證呢?

解決方法一
引入synchronized關鍵字
public synchronized static SingletonDemo getInstance() {
if(instance == null) {
instance = new SingletonDemo();
}
return instance;
}
輸出結果:

我們能夠發(fā)現,通過引入Synchronized關鍵字,能夠解決高并發(fā)環(huán)境下的單例模式問題
但是synchronized屬于重量級的同步機制,它只允許一個線程同時訪問獲取實例的方法,但是為了保證數據一致性,而減低了并發(fā)性,因此采用的比較少
解決方法二
通過引入DCL Double Check Lock雙端檢鎖機制
public static SingletonDemo getInstance() {
if(instance == null) {
// 同步代碼段的時候,進行檢測
synchronized (SingletonDemo.class) {
if(instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
最后輸出的結果為:

從輸出結果來看,確實能夠保證單例模式的正確性,但是上面的方法還是存在問題的
DCL(雙端檢鎖)機制不一定是線程安全的,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因是在某一個線程執(zhí)行到第一次檢測的時候,讀取到 instance 不為null,instance的引用對象可能沒有完成實例化。因為 instance = new SingletonDemo();可以分為以下三步進行完成:
- memory = allocate(); // 1、分配對象內存空間
- instance(memory); // 2、初始化對象
- instance = memory; // 3、設置instance指向剛剛分配的內存地址,此時instance != null
但是我們通過上面的三個步驟,能夠發(fā)現,步驟2 和 步驟3之間不存在 數據依賴關系,而且無論重排前 還是重排后,程序的執(zhí)行結果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的。
- memory = allocate(); // 1、分配對象內存空間
- instance = memory; // 3、設置instance指向剛剛分配的內存地址,此時instance != null,但是對象還沒有初始化完成
- instance(memory); // 2、初始化對象
這樣就會造成什么問題呢?
也就是當我們執(zhí)行到重排后的步驟2,試圖獲取instance的時候,會得到null,因為對象的初始化還沒有完成,而是在重排后的步驟3才完成,因此執(zhí)行單例模式的代碼時候,就會重新在創(chuàng)建一個instance實例
指令重排只會保證串行語義的執(zhí)行一致性(單線程),但并不會關系多線程間的語義一致性
所以當一條線程訪問instance不為null時,由于instance實例未必已初始化完成,這就造成了線程安全的問題
所以需要引入volatile,來保證出現指令重排的問題,從而保證單例模式的線程安全性
private static volatile SingletonDemo instance = null;
最終代碼
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo () {
System.out.println(Thread.currentThread().getName() + "\t 我是構造方法SingletonDemo");
}
public static SingletonDemo getInstance() {
if(instance == null) {
// a 雙重檢查加鎖多線程情況下會出現某個線程雖然這里已經為空,但是另外一個線程已經執(zhí)行到d處
synchronized (SingletonDemo.class) //b
{
//c不加volitale關鍵字的話有可能會出現尚未完全初始化就獲取到的情況。原因是內存模型允許無序寫入
if(instance == null) {
// d 此時才開始初始化
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());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
SpringBoot整合FastDFS中間件實現文件分布管理
FastDFS是一個開源的輕量級分布式文件系統(tǒng),它對文件進行管理,功能包括:文件存儲、文件同步、文件上傳、文件下載等,解決了大容量存儲和負載均衡的問題,本文介紹了SpringBoot整合FastDFS中間件實現文件分布管理,需要的朋友可以參考下2024-08-08
springboot做代理分發(fā)服務+代理鑒權的實現過程
這篇文章主要介紹了springboot做代理分發(fā)服務+代理鑒權的實現過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
Mybatis foreach用法解析--對于list和array
這篇文章主要介紹了Mybatis foreach用法解析--對于list和array,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

