1. 什么是單例模式?
提起單例模式,就必須介紹設(shè)計模式,而設(shè)計模式就是在軟件設(shè)計中,針對特殊問題提出的解決方案。它是多年來針對一些常見的問題的解決方法,具有良好的可復(fù)用性、可擴展性和可維護性。
標(biāo)準(zhǔn)的設(shè)計模式有23種,單例模式就是最常見的一種,其目的是確認(rèn)一個類只有一個實例對象,并且可以提供一個全局訪問點來訪問該實例。
單例就是指一個類的實例只有一個,即該類的所有對象都指向用同一個實例。
而多數(shù)單例模式并沒有結(jié)合多線程,在多線程環(huán)境下運行有時會出現(xiàn)線程安全問題,所以下面不僅介紹如何實現(xiàn)單例模式,還有單例模式結(jié)合多線程使用時的相關(guān)知識。
2. 立即加載/“餓漢模式”
立即加載一般還被稱作餓漢模式,根據(jù)立即,餓漢可以看出十分的急,所以在餓漢模式中,這樣單例中的唯一實例對象就被創(chuàng)建。
創(chuàng)建MyObject.java代碼如下:
//單例模式、餓漢模式
public class MyObject {
//進行封裝,防止創(chuàng)建新的對象
private static MyObject object = new MyObject();
private MyObject(){};
//通過這個方法獲得對象
public static MyObject getObject(){
return object;
}
}
創(chuàng)建線程類MyThread.java:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(MyObject.getObject().hashCode());
}
}
創(chuàng)建運行類Run.java:
public class Run {
//測試單例模式對象是同一個
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
運行結(jié)果:
運行結(jié)果相同,說明對象是同一個,成功實現(xiàn)了立即加載型單例設(shè)計模式。
3. 延時加載/“懶漢模式”
延時,懶漢可以看出代碼并不著急,所以懶漢模式型單例模式中的對象并不像餓漢模式中沒有調(diào)用前就創(chuàng)建完成,而是在第一調(diào)用方法實例時才被創(chuàng)建。
對比餓漢模式:
優(yōu)點:會減少內(nèi)存資源浪費。
缺點:多線程環(huán)境并發(fā)運行,可能會出現(xiàn)線程安全。
3.1 第一版
創(chuàng)建類MyObjectLazy.java,代碼如下:
public class MyObjectLazy {
//單例模式、懶漢模式
private static MyObjectLazy myObjectLazy = null;
private static Object lock = new Object();
private MyObjectLazy(){};
public static MyObjectLazy getMyObjectLazy(){
if(myObjectLazy != null){
return myObjectLazy;
}else{
myObjectLazy = new MyObjectLazy();
}
return myObjectLazy;
}
}
創(chuàng)建線程類MyThreadLazy.java:
public class MyThreadLazy extends Thread{
@Override
public void run() {
System.out.println(MyObjectLazy.getMyObjectLazy().hashCode());
}
}
創(chuàng)建運行類RunLazy.java:
public class RunLazy {
//測試對象是不是同一個,是的話就是安全的單例模式
public static void main(String[] args) {
MyThreadLazy t1 = new MyThreadLazy();
MyThreadLazy t2 = new MyThreadLazy();
MyThreadLazy t3 = new MyThreadLazy();
t1.start();
t2.start();
t3.start();
}
}
運行結(jié)果:
結(jié)果不同,所以并不是單例模式,其中有問題,造成線程不安全。
3.2 第二版
第一版生成不同對象,所以造成非線程安全,我們可以做出一點修改,對代碼加上鎖。
修改后的MyObjectLazy.java:
public static MyObjectLazy getMyObjectLazy(){
synchronized (lock){
if(myObjectLazy == null){
myObjectLazy = new MyObjectLazy();
}
}
return myObjectLazy;
}
運行結(jié)果:
說明這個單例模式是正確實現(xiàn)了。
3.3 第三版
但是第二版又暴露一個問題,上面加鎖后相當(dāng)于整個方法都加鎖,上面一個線程沒有釋放鎖,下一個線程將無法運行,造成效率低下。
所以我們繼續(xù)修改,修改后的MyObjectLazy.java:
public static MyObjectLazy getMyObjectLazy(){
try{
if(myObjectLazy == null){
Thread.sleep(1000);
synchronized (lock){
myObjectLazy = new MyObjectLazy();
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
return myObjectLazy;
}
運行結(jié)果:
運行結(jié)果又不同了,創(chuàng)建出了三個對象,為什么?這是因為雖然上了鎖,但是if已經(jīng)判斷,只是new對象時串行。
雖然效率提高了,但這并不是單例模式。
3.4 第四版
我們可以使用DCL雙檢查鎖機制來實現(xiàn),進行兩次if判斷,使線程安全。
修改后MyObjectLazy.java:文章來源:http://www.zghlxwxcb.cn/news/detail-690405.html
//再一次修改代碼,加鎖只加一塊,并且應(yīng)用DCL雙檢查機制來實現(xiàn)多線程環(huán)境下的延時加載單例模式,保證線程安全
public static MyObjectLazy getMyObjectLazy(){
try{
if(myObjectLazy == null){
Thread.sleep(1000);
synchronized (lock){
if(myObjectLazy == null) {
myObjectLazy = new MyObjectLazy();
}
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
return myObjectLazy;
}
運行結(jié)果:
使用雙檢查鎖功能,成功解決了懶漢模式遇到多線程的問題。DCL經(jīng)常出現(xiàn)在此場景下,我們要學(xué)會應(yīng)用。文章來源地址http://www.zghlxwxcb.cn/news/detail-690405.html
到了這里,關(guān)于【多線程案例】單例模式(懶漢模式和餓漢模式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!