单例模式中的懒汉式以及线程安全性问题
先看代碼:
package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static Singleton2 getInstance(){if(instance == null) {//1:讀取instance的值instance = new Singleton2();//2: 實例化instance}return instance;}} package com.roocon.thread.t5;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class MultiThreadMain {public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(20);for (int i = 0; i< 20; i++) {threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+":"+Singleton2.getInstance());}});} threadPool.shutdown(); } }運行結果:
pool-1-thread-4:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-14:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-10:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-8:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-5:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-12:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-1:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-9:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-6:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-2:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-16:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-3:com.roocon.thread.t5.Singleton2@1c208db1 pool-1-thread-17:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-13:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-18:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-7:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-20:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-11:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-15:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-19:com.roocon.thread.t5.Singleton2@6519891a發現,有個實例是Singleton2@1c208db1,也就說明,返回的不是同一個實例。這就是所謂的線程安全問題。
解釋原因:對于以上代碼注釋部分,如果此時有兩個線程,線程A執行到1處,讀取了instance為null,然后cpu就被線程B搶去了,此時,線程A還沒有對instance進行實例化。
因此,線程B讀取instance時仍然為null,于是,它對instance進行實例化了。然后,cpu就被線程A搶去了。此時,線程A由于已經讀取了instance的值并且認為它為null,所以,
再次對instance進行實例化。所以,線程A和線程B返回的不是同一個實例。
?
那么,如何解決呢?
1.在方法前面加synchronized修飾。這樣肯定不會再有線程安全問題。
package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static synchronized Singleton2 getInstance(){if(instance == null) {//1instance = new Singleton2();//2}return instance;}}但是,這種解決方式,假如有100個線程同時執行,那么,每次去執行getInstance方法時都要先獲得鎖再去執行方法體,如果沒有鎖,就要等待,耗時長,感覺像是變成了串行處理。因此,嘗試其他更好的處理方式。
2. 加同步代碼塊,減少鎖的顆粒大小。我們發現,只有第一次instance為null的時候,才去創建實例,而判斷instance是否為null是讀的操作,不可能存在線程安全問題,因此,我們只需要對創建實例的代碼進行同步代碼塊的處理,也就是所謂的對可能出現線程安全的代碼進行同步代碼塊的處理。
package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static Singleton2 getInstance(){if(instance == null) {synchronized (Singleton2.class){instance = new Singleton2();}}return instance;}}但是,這樣處理就沒有問題了嗎?同樣的原理,線程A和線程B,線程A讀取instance值為null,此時cpu被線程B搶去了,線程B再來判斷instance值為null,于是,它開始執行同步代碼塊中的代碼,對instance進行實例化。此時,線程A獲得cpu,由于線程A之前已經判斷instance值為null,于是開始執行它后面的同步代碼塊代碼。它也會去對instance進行實例化。
這樣就導致了還是會創建兩個不一樣的實例。
那么,如何解決上面的問題。
很簡單,在同步代碼塊中instance實例化之前進行判斷,如果instance為null,才對其進行實例化。這樣,就能保證instance只會實例化一次了。也就是所謂的雙重檢查加鎖機制。
再次分析上面的場景:
線程A和線程B,線程A讀取instance值為null,此時cpu被線程B搶去了,線程B再來判斷instance值為null。于是,它開始執行同步代碼塊代碼,對instance進行了實例化。這是線程A獲得cpu執行權,當線程A去執行同步代碼塊中的代碼時,它再去判斷instance的值,由于線程B執行完后已經將這個共享資源instance實例化了,所以instance不再為null,所以,線程A就不會再次實行實例化代碼了。
package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static synchronized Singleton2 getInstance(){if(instance == null) {synchronized (Singleton2.class){if (instance == null){instance = new Singleton2();}}}return instance;}}但是,雙重檢查加鎖并不代碼百分百一定沒有線程安全問題了。因為,這里會涉及到一個指令重排序問題。instance = new Singleton2()其實可以分為下面的步驟:
1.申請一塊內存空間;
2.在這塊空間里實例化對象;
3.instance的引用指向這塊空間地址;
指令重排序存在的問題是:
對于以上步驟,指令重排序很有可能不是按上面123步驟依次執行的。比如,先執行1申請一塊內存空間,然后執行3步驟,instance的引用去指向剛剛申請的內存空間地址,那么,當它再去執行2步驟,判斷instance時,由于instance已經指向了某一地址,它就不會再為null了,因此,也就不會實例化對象了。這就是所謂的指令重排序安全問題。那么,如何解決這個問題呢?
加上volatile關鍵字,因為volatile可以禁止指令重排序。
package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static volatile Singleton2 instance;public static Singleton2 getInstance(){if(instance == null) {synchronized (Singleton2.class){if (instance == null){instance = new Singleton2();}}}return instance;} }轉載于:https://www.cnblogs.com/mike-JP/p/11084316.html
總結
以上是生活随笔為你收集整理的单例模式中的懒汉式以及线程安全性问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV2:开头篇 介绍
- 下一篇: MVN 报错1