Java中的單例模式的資料已經有很多了,很優秀的也有許多,但是我的博客,還是爲小白寫的。(因爲我也是小白啊!)希望你可以按我的步驟一起學習一下,我已經寫的很詳細了,寫一遍大於看一遍!!!如果你是大神也不要緊,你可以看直接這篇博客(我大部分想法都來源於他),或者幫我看看我有沒有錯,也可以看看有沒有用當年沒有搞清楚的。----(寫所有給看的人)最重要的是希望你能幫我看看哪裏不對,或者有問題,不合理都可以告訴我,我們一起學習一下。(謝謝啦)
聯繫方式:QQ:1441289873
Java的的中有衆多設計模式,其中最常用的就是單例模式。大家肯定看到許多單例模式和用過,接下我先說一下單例模式的區別和用法,然後從簡單的開始學習。
單例模式:單例對象能保證在一個JVM中,該對象只有一個實例存在。
先簡單說一下,各自的區別:
常見的五種單例模式實現方式主要:
- 餓漢式(線程安全,調用效率高;但是不能延時加載)
- 懶漢式(線程安全,調用效率不高,但是可以延時加載)
其他:
- 雙重檢測鎖式(由於JVM底層內部模型原因,偶爾會出問題,不建議使用)
- 靜態內部類式(線程安全,調用效率高,但是,可以延時加載)
- 枚舉類(線程安全,調用效率高,防反射和反序列化,不能延時加載)如何選用?
- 單例對象佔用資源少,不需要延時加載時:枚舉類好於餓漢式 - 單例對象佔用資源大,需要延時加載時:
靜態內部類式好於懶漢式
一、接下來從最簡單的單例模式開始吧直接上代碼:
public class Android {
/*聲明一個變量等於null,實現了延遲加載*/
private static Android android = null;
/*構造方法私有化,防止被new出來*/
private Android() {
}
/*調用這個方法時判斷對象不存在時創建對象*/
public static Android getinstance() {
if (android == null) {
android = new Android();
}
return android;
}
}
大部分看過單例文章,都知道這個單例線程不安全唄。爲什麼不安全呢,當時小白的我就沒有搞懂,尤其是我這種自學的單例,只是抄別人的,什麼都不懂。(哈哈)
哪裏不安全呢,我給大家一段測試代碼,你就可以看出來了。
public class ExampleUnitTest {
@Test
public void test() {
MyThread myThread =new MyThread();
MyThread myThread1 =new MyThread();
myThread.start();
myThread1.start();
}
class MyThread extends Thread {
@Override
public void run() {
Android getinstance = Android.getinstance();
System.out.println("多線程下: " + getinstance.toString());
}
}
}
我的第一次接下是測試結果:
多線程下:com.example.a14412.myapplication.Android@2957fcb0
多線程下:com.example.a14412.myapplication.Android@475cf196
有同學說了他的結果和我一樣是這樣的:
多線程下:com.example.a14412.myapplication.Android@1a9841c0
多線程下:com.example.a14412.myapplication.Android@1a9841c0
不要緊,我來解釋一下原因。第一次的結果是這樣來的。因爲代碼的執行速度很快,即便是兩個線程。一個線程走到如果判斷時,另一個也到了,如果判斷,他們兩個都得到對象是空的結果,就各自實例化了對象。(這也是線程不安全的原因)
第二次的結果原因大家應該理解了,就是說,一個線程快,一個線程慢,快的實例化後對象。第二個線程才判斷,此時的對象已經被實例化過了,所以如果判斷是不等於空。
線程的速度爲什麼會快慢呢,這時的你需要理解多線程去了。
2.加鎖的單例模式
在多線程模式下,大家能想到第一個就是同步的,這個關鍵字吧。這個關鍵字我就不在這說了。直接上代碼吧!
這個單例是爲了彌補上面單例在多線程下實例了兩個對象的不足。
public class Android {
private static Android android = null;
private Android() {
}
public synchronized static Android getinstance() {
if (android == null) {
android = new Android();
}
return android;
}
}
對,基本沒有變化只是加了一個同步的關鍵字而已看測試效果:
第一次:
多線程下:com.example.a14412.myapplication.Android@6b5b9135
多線程下:com.example.a14412.myapplication.Android@6b5b9135
第二次:
多線程下:com.example.a14412.myapplication.Android@1a9841c0
第三次
已經結束了,對你猜對了,第三次沒有打印任何東西。你說你是不這樣的,好吧。你先看看和我的代碼一樣嗎?
如果一樣的話,請多運行幾次,看看。這三種結果,都遇到了嗎?
遇到了,我們繼續。
原因很簡單啊,主線程先結束了,子線程執行不到打印的代碼了。修改加個睡眠試試。
public class ExampleUnitTest {
@Test
public void test() {
MyThread myThread =new MyThread();
MyThread myThread1 =new MyThread();
myThread.start();
myThread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class MyThread extends Thread {
@Override
public void run() {
Android getinstance = Android.getinstance();
System.out.println("多線程下: " + getinstance.toString());
}
}
}
打印結果:
第一次:
多線程下:com.example.a14412.myapplication.Android@1a9841c0
多線程下:com.example.a14412.myapplication.Android@1a9841c0
第二次:
多線程下:com.example.a14412.myapplication.Android@6b5b9135
多線程下:com.example.a14412.myapplication.Android@6b5b9135
第三次:
多線程下:com.example.a14412.myapplication.Android@2608dd6c
多線程下:com.example.a14412.myapplication.Android@2608dd6c
這個單例,沒有問題了?但是我們可以優化的。爲什麼要優化呢。從性能上看,當一個線程進入能得到同步鎖,加上鎖以後,第二個線程只能等待。如果這時那個線程已經實例化對象,由於有同步鎖,另一個線程還要等待。小程序就沒事了,如果程序大了。每次實例化這個對象,都要有些線程在等待。,就會很耗時和耗性能。上代碼
3.雙重檢測鎖式
public class Android {
private static Android android = null;
private Android() {
}
public static Android getinstance() {
if (android == null) {
synchronized (Android.class) {
if (android==null) {
android = new Android();
}
}
}
return android;
}
}
雙重如果判斷,這是已經搞定了。但是這個單例還是不推薦。如果雙重,容易出問題。在程序大了,代碼複雜了,就不是這麼簡單的使用了。說不定會出現什麼未知情況,比如死鎖啊,等待。以上都是懶漢式是單例。(延時加載)想繼續瞭解看這篇的Java的單例模式中雙重檢查鎖的問題
那我也就不羅嗦了,寫我最終版的單例模式吧!
4.內部類單例
先上代碼:
public class Android {
private Android() {
}
public static Android getAndroidHolder(){
return AndroidHolder.android;
}
private static class AndroidHolder{
private final static Android android=new Android();
}
}
內部類的寫法。在類裏寫個內部類。由於內部類是私有的只有Androi可以訪問的。那怎麼訪問,這不是還有一個public static的方法嗎?由於內部類是靜的,就可以使用AndroidHolder.android獲取的就是新的Android的這個對象了。
優點:線程安全,保證單例的唯一性,延遲了對象的實例化。
看測試代碼:
public class ExampleUnitTest {
@Test
public void test() {
MyThread myThread =new MyThread();
MyThread myThread1 =new MyThread();
myThread.start();
myThread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class MyThread extends Thread {
@Override
public void run() {
Android androidHolder = Android.getAndroidHolder();
//打印的是地址值
System.out.println("多線程下1: " + androidHolder.toString());
//打印的是類名
System.out.println("多線程下1: " + androidHolder.getClass().getName());
}
}
再看結果:
多線程下1:com.example.a14412.myapplication.Android@29abcaca
多線程下1:com.example.a14412.myapplication.Android
多線程下1:com.example.a14412.myapplication.Android@29abcaca
多線程下1 :com.example.a14412.myapplication.Android
單例模式的優點:
1.在內存中只有一個實例,減少內存開支
2.只生產一個實例,減少系統性能的性能開銷
3.避免對資源的多重佔用。
4.可以在系統設置全局的訪問點,優化和共享資源訪問。(例如可以設置一個單例類,負責所有數據表的映射處理)
單例的缺點;
1.單例一般沒有接口,擴展很困難。
2.單例如果持有Context對象,很容易引起內存泄漏,最好傳遞全局的Application Context。
單例模式到這就結束了,安卓開發的有福利了。送你給插件,SingletonTest。
SingletonTest用法很簡單,百度一下就好。