什么是設(shè)計(jì)模式衷快?
是一套被反復(fù)使用宙橱、多數(shù)人知曉的、經(jīng)過(guò)分類的蘸拔、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)师郑。一些開(kāi)發(fā)的套路,用于解決某一些特定場(chǎng)景的思想调窍。
什么是單例設(shè)計(jì)模式宝冕?
是一種最最常見(jiàn)的一種模式,保證整個(gè)程序中只有一個(gè)實(shí)例陨晶,這是最基本的猬仁,真正做到整個(gè)系統(tǒng)中唯一并不容易,通常還要考慮反射破壞先誉、序列化/反序列化湿刽、對(duì)象垃圾回收等問(wèn)題。
參考文章:https://blog.csdn.net/tjiyu/article/details/76572617
單例模式的幾種實(shí)現(xiàn)方式
單例實(shí)現(xiàn)方式1.餓漢式
/**
* 餓漢式-隨著類的加載會(huì)自動(dòng)new出對(duì)象
*/
public class Singleton {
private Singleton() {
}//私有化構(gòu)造器
private static Singleton mInstance = new Singleton();
public static Singleton getInstance() {
return mInstance;
}
}
餓漢式褐耳,簡(jiǎn)單可用诈闺。這種方式比較常用,它基于JVM的類加載器機(jī)制避免了多線程的同步問(wèn)題铃芦,對(duì)象在類裝載時(shí)就實(shí)例化雅镊,所以稱為餓漢式。
Lazy 初始化:否刃滓;
多線程安全:是仁烹;
優(yōu)點(diǎn):沒(méi)有加鎖,執(zhí)行效率會(huì)提高咧虎。
缺點(diǎn):沒(méi)有Lazy初始化卓缰,可能有時(shí)候不需要使用,浪費(fèi)內(nèi)存砰诵。
單例實(shí)現(xiàn)方式2.懶漢式1
多線程并發(fā)的時(shí)候會(huì)有問(wèn)題征唬,有可能會(huì)存在多個(gè)實(shí)例mInstance
/**
* 懶漢式1-多線程并發(fā)的時(shí)候會(huì)有問(wèn)題,有可能會(huì)存在多個(gè)實(shí)例mInstance
*/
public class Singleton {
private Singleton() {
}//私有化構(gòu)造器
private static Singleton mInstance;
public static Singleton getInstance() {
if (mInstance == null)
mInstance = new Singleton();
return mInstance;
}
}
Lazy 初始化:是茁彭;
多線程安全:否总寒;
能夠在getInstance()時(shí)再創(chuàng)建對(duì)象,所以稱為懶漢式理肺。這種實(shí)現(xiàn)最大的問(wèn)題就是不支持多線程摄闸。因?yàn)闆](méi)有加鎖同步善镰。
單例實(shí)現(xiàn)方式3.懶漢式2
解決多線程并發(fā)問(wèn)題,加一個(gè)同步鎖贪薪,但是又會(huì)出現(xiàn)效率問(wèn)題媳禁,就是每次getInstanc的時(shí)候都會(huì)經(jīng)過(guò)同步鎖的判斷!
/**
* 懶漢式2-解決多線程并發(fā)問(wèn)題画切,加一個(gè)同步鎖,但是又會(huì)出現(xiàn)效率問(wèn)題囱怕,就是每次getInstanc的時(shí)候都會(huì)經(jīng)過(guò)同步鎖的判斷霍弹!
*/
public class Singleton {
private Singleton() {
}//私有化構(gòu)造器
private static Singleton mInstance;
public static synchronized Singleton getInstance() {
if (mInstance == null)
mInstance = new Singleton();
return mInstance;
}
}
Lazy 初始化:是
多線程安全:是
除第一次使用,后面getInstance()不需要同步娃弓;每次同步典格,效率很低。
單例實(shí)現(xiàn)方式4.懶漢式3(DCL)
雙重檢查台丛,只會(huì)執(zhí)行一次同步鎖耍缴。(這種方式其實(shí)還是有問(wèn)題!引出volatile關(guān)鍵字)
/**
* 懶漢式3-雙重檢查挽霉,只會(huì)執(zhí)行一次同步鎖防嗡。(這種方式其實(shí)還是有問(wèn)題!引出volatile關(guān)鍵字)
*/
public class Singleton {
private Singleton() {
}//私有化構(gòu)造器
private static Singleton mInstance;
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
Lazy 初始化:是侠坎;
多線程安全:是蚁趁;
這種方式采用雙鎖機(jī)制,安全且在多線程情況下能保持高性能实胸。
實(shí)例變量需要加volatile 關(guān)鍵字保證易變可見(jiàn)性他嫡,JDK1.5起才可用,引出下面示例庐完。
單例實(shí)現(xiàn)方式5.懶漢式4
/**
* 懶漢式4-雙重檢查钢属,只會(huì)執(zhí)行一次同步鎖+volatile關(guān)鍵字
*/
public class Singleton {
private Singleton() {
}//私有化構(gòu)造器
private static volatile Singleton mInstance;
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
volatile關(guān)鍵字講解
volatile關(guān)鍵字作用
- 防止重排序
- 線程可見(jiàn)性 - 某一個(gè)線程改了公用對(duì)象(變量),短時(shí)間內(nèi)另一個(gè)線程可能是不可見(jiàn)的门躯,因?yàn)槊恳粋€(gè)線程都有自己的緩存區(qū)(線程工作區(qū))
參考文章:
http://www.cnblogs.com/dolphin0520/p/3920373.html
https://www.cnblogs.com/zhengbin/p/5654805.html
public class TestValatileDemo1 {
private static class MyRunnable implements Runnable {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag = " + flag);
}
public boolean isFlag() {
return flag;
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
while (true) {
if (runnable.isFlag()) {
System.out.println("----------------");
break;
}
}
}
}
運(yùn)行上述代碼淆党,控制臺(tái)輸出:
flag = true
只有flag= true,沒(méi)有分割線輸出生音,且程序沒(méi)有終止退出宁否。
改動(dòng)1:如果在while(true)語(yǔ)句塊中加入線程休眠:
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
while (true) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (runnable.isFlag()) {
System.out.println("----------------");
break;
}
}
}
運(yùn)行上述代碼,控制臺(tái)輸出:
flag = true
----------------
Process finished with exit code 0
修改2:去掉上面的線程睡眠缀遍,對(duì)flag變量加入volatile關(guān)鍵字修飾:
private static class MyRunnable implements Runnable {
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag = " + flag);
}
public boolean isFlag() {
return flag;
}
}
運(yùn)行上述代碼慕匠,控制臺(tái)輸出:
----------------
flag = true
Process finished with exit code 0
小結(jié):
可見(jiàn)性,是指線程之間的可見(jiàn)性域醇,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見(jiàn)的台谊。也就是一個(gè)線程修改的結(jié)果蓉媳。另一個(gè)線程馬上就能看到。比如:用volatile修飾的變量锅铅,就會(huì)具有可見(jiàn)性酪呻。volatile修飾的變量不允許線程內(nèi)部緩存和重排序,即直接修改內(nèi)存盐须。所以對(duì)其他線程是可見(jiàn)的玩荠。但是這里需要注意一個(gè)問(wèn)題,volatile只能讓被他修飾內(nèi)容具有可見(jiàn)性贼邓,但不能保證它具有原子性阶冈。比如 volatile int a = 0;之后有一個(gè)操作 a++塑径;這個(gè)變量a具有可見(jiàn)性女坑,但是a++ 依然是一個(gè)非原子操作,也就是這個(gè)操作同樣存在線程安全問(wèn)題统舀。
Volatile原理
Java語(yǔ)言提供了一種稍弱的同步機(jī)制匆骗,即volatile變量,用來(lái)確保將變量的更新操作通知到其他線程誉简。當(dāng)把變量聲明為volatile類型后碉就,編譯器與運(yùn)行時(shí)都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序描融。volatile變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見(jiàn)的地方铝噩,因此在讀取volatile類型的變量時(shí)總會(huì)返回最新寫(xiě)入的值。
在訪問(wèn)volatile變量時(shí)不會(huì)執(zhí)行加鎖操作窿克,因此也就不會(huì)使執(zhí)行線程阻塞骏庸,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級(jí)的同步機(jī)制。
當(dāng)對(duì)非 volatile 變量進(jìn)行讀寫(xiě)的時(shí)候年叮,每個(gè)線程先從內(nèi)存拷貝變量到CPU緩存中具被。如果計(jì)算機(jī)有多個(gè)CPU,每個(gè)線程可能在不同的CPU上被處理只损,這意味著每個(gè)線程可以拷貝到不同的 CPU cache 中一姿。
而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀跃惫,跳過(guò) CPU cache 這一步叮叹。
當(dāng)一個(gè)變量定義為 volatile 之后,將具備兩種特性:
1.保證此變量對(duì)所有的線程的可見(jiàn)性爆存,這里的“可見(jiàn)性”蛉顽,如本文開(kāi)頭所述,當(dāng)一個(gè)線程修改了這個(gè)變量的值先较,volatile 保證了新值能立即同步到主內(nèi)存携冤,以及每次使用前立即從主內(nèi)存刷新悼粮。但普通變量做不到這點(diǎn),普通變量的值在線程間傳遞均需要通過(guò)主內(nèi)存(詳見(jiàn):Java內(nèi)存模型)來(lái)完成曾棕。
2.禁止指令重排序優(yōu)化扣猫。有volatile修飾的變量,賦值后多執(zhí)行了一個(gè)“l(fā)oad addl $0x0, (%esp)”操作翘地,這個(gè)操作相當(dāng)于一個(gè)內(nèi)存屏障(指令重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置)申尤,只有一個(gè)CPU訪問(wèn)內(nèi)存時(shí),并不需要內(nèi)存屏障衙耕;(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開(kāi)發(fā)送給各相應(yīng)電路單元處理)瀑凝。
volatile 性能:
volatile 的讀性能消耗與普通變量幾乎相同,但是寫(xiě)操作稍慢臭杰,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來(lái)保證處理器不發(fā)生亂序執(zhí)行。
單例實(shí)現(xiàn)方式6.靜態(tài)內(nèi)部類(也是比較常用谚中,保證了線程安全)
/**
* 單例實(shí)現(xiàn)方式6.靜態(tài)內(nèi)部類(也是比較常用渴杆,保證了線程安全)
*/
public class Singleton {
private Singleton() {//私有化構(gòu)造器
}
public static Singleton getInstance() {
return SingletonHolder.mInstance;
}
public static class SingletonHolder {
private static volatile Singleton mInstance = new Singleton();
}
}
Lazy 初始化:是;
多線程安全:是宪塔;
同樣利用了JVM類加載機(jī)制來(lái)保證初始化實(shí)例對(duì)象時(shí)只有一個(gè)線程磁奖,靜態(tài)內(nèi)部類SingletonHolder 類只有第一次調(diào)用 getInstance 方法時(shí),才會(huì)裝載從而實(shí)例化對(duì)象某筐。
單例實(shí)現(xiàn)方式7.容器管理
/**
* 單例實(shí)現(xiàn)方式7.容器管理
*/
public class Singleton {
private static Map<String, Object> mSingleMap = new HashMap<>();
private Singleton() {//私有化構(gòu)造器
}
static {
mSingleMap.put("SingletonInstance", new Singleton());
}
public static Object getInstance(String key) {
return mSingleMap.get(key);
}
}
Android中的系統(tǒng)服務(wù)中比較常見(jiàn):
final class SystemServiceRegistry {
private static final String TAG = "SystemServiceRegistry";
// Service registry information.
// This information is never changed once static initialization has completed.
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
private static int sServiceCacheSize;
// Not instantiable.
private SystemServiceRegistry() { }
static {
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
//....省略....
}
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
/**
* Gets the name of the system-level service that is represented by the specified class.
*/
public static String getSystemServiceName(Class<?> serviceClass) {
return SYSTEM_SERVICE_NAMES.get(serviceClass);
}
/**
* Statically registers a system service with the context.
* This method must be called during static initialization only.
*/
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
}
單例實(shí)現(xiàn)方式8.單例(《Effective Java》推薦比搭,不常見(jiàn))
/**
* 單例實(shí)現(xiàn)方式8.枚舉
*/
public enum Singleton {
//定義一個(gè)枚舉的元素,就代表Singleton實(shí)例
INSTANCE;
/*
**假如還定義有下面的方法南誊,調(diào)用:Singleton.INSTANCE.doSomethingMethod();
*/
public void doSomethingMethod() {
}
}
Lazy 初始化:否身诺;
多線程安全:是;
從Java1.5開(kāi)始支持enum特性抄囚;無(wú)償提供序列化機(jī)制霉赡,絕對(duì)防止多次實(shí)例化,即使在面對(duì)復(fù)雜的序列化或者反射攻擊的時(shí)候幔托。
不過(guò)穴亏,用這種方式寫(xiě)不免讓人感覺(jué)生疏,這種實(shí)現(xiàn)方式還沒(méi)有被廣泛采用重挑,但這是實(shí)現(xiàn)單例模式的最佳方法嗓化。
開(kāi)發(fā)中常用的幾種(前提是必須線程安全):
- 餓漢式——一般情況下都可以使用,最簡(jiǎn)單谬哀,最近本
- 靜態(tài)內(nèi)部類的方式——需要演示加載的時(shí)候使用
- 雙重檢查的鎖DCL——面試的時(shí)候會(huì)問(wèn)刺覆,也是比較常見(jiàn)
- 枚舉——高安全性的情景下使用
上面8種Java單例模式實(shí)現(xiàn)方式除枚舉方式外,其他的給實(shí)例構(gòu)造函數(shù)protected或private權(quán)限玻粪,依然可以通過(guò)相關(guān)反射方法隅津,改變其權(quán)限诬垂,創(chuàng)建多個(gè)實(shí)例。
public class Test {
public static void main(String args[]) {
Singleton singleton = Singleton.getInstance();
try {
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singletonnew = constructor.newInstance();
System.out.println(singleton == singletonnew);
} catch (Exception e) {
}
}
}
輸出結(jié)果:false
可以給構(gòu)造函數(shù)加上判斷伦仍,限制創(chuàng)建多個(gè)實(shí)例结窘,如下:
private Singleton() {
if (null != Singleton.singleton) {
throw new RuntimeException();
}
}
單例模式在Android中的應(yīng)用場(chǎng)景——Activity的管理類
Activity的管理類可以統(tǒng)一管理Activity的生命周期和操作Activity方法。如A充蓝、B隧枫、C三個(gè)Activity界面,點(diǎn)擊C界面一個(gè)按鈕直接返回到A界面谓苟,B界面自動(dòng)移除的操作官脓。再如單點(diǎn)登錄,全局Activity都要彈出一個(gè)Dialog提示框的場(chǎng)景涝焙。
方案有:
EventBus(純反射效率低)卑笨、廣播(有延遲)、集合管理仑撞、SingleTask(只適用于部分場(chǎng)景)赤兴。
接下來(lái)我們寫(xiě)一個(gè)Activity管理類來(lái)實(shí)現(xiàn)上面的場(chǎng)景1.
ActivityManager:
public class ActivityManager {
String TAG = "ActivityManager";
private static volatile ActivityManager mInstance;
private Stack<Activity> mActivities;
private ActivityManager() {
mActivities = new Stack<>();
}
public static ActivityManager getInstance() {
if (mInstance == null) {
synchronized (ActivityManager.class) {
if (mInstance == null) {
mInstance = new ActivityManager();
}
}
}
return mInstance;
}
// 添加Activity
public void attach(Activity activity) {
Log.d(TAG, "attach " + activity);
mActivities.add(activity);
}
// 移除Activity
/*public void detach(Activity target) {
for (Activity activity : mActivities) {
if (activity == target) {
Log.d(TAG, "detach " + activity);
mActivities.remove(target);//todo 一邊循環(huán)一邊移除是有問(wèn)題的
}
}
}*/
// 移除Activity-修改版
public void detach(Activity target) {
int size = mActivities.size();
for (int i = 0; i < size; i++) {
Activity activity = mActivities.get(i);
if (activity == target) {
mActivities.remove(i);
i--;
size--;
}
}
}
// 根據(jù)Activity關(guān)閉Activity
/*public void finish(Activity target) {
for (Activity activity : mActivities) {
if (activity == target) {
Log.d(TAG, "detach " + activity);
mActivities.remove(target);//todo 一邊循環(huán)一邊移除是有問(wèn)題的
target.finish();
}
}
}*/
// 根據(jù)Activity關(guān)閉Activity-修改版
public void finish(Activity target) {
Log.d(TAG, "finish canonicalName:" + target.getClass().getCanonicalName());
int size = mActivities.size();
for (int i = 0; i < size; i++) {
Activity activity = mActivities.get(i);
if (activity == target) {
mActivities.remove(i);
target.finish();
i--;
size--;
}
}
}
// 根據(jù)Class關(guān)閉Activity
/*public void finish(Class<? extends Activity> targetClass) {
Log.d(TAG, "finish canonicalName:" + targetClass.getCanonicalName());
//com.ivyzh.designpatterndemo.d1_singleton_pattern.activities.BMainActivity
for (Activity activity : mActivities) {
if (activity.getClass().getCanonicalName().equals(targetClass.getCanonicalName())) {
mActivities.remove(activity);//todo 一邊循環(huán)一邊移除是有問(wèn)題的
activity.finish();
}
}
}*/
// 根據(jù)Class關(guān)閉Activity-修改版
public void finish(Class<? extends Activity> targetClass) {
Log.d(TAG, "finish canonicalName:" + targetClass.getCanonicalName());
//com.ivyzh.designpatterndemo.d1_singleton_pattern.activities.BMainActivity
int size = mActivities.size();
for (int i = 0; i < size; i++) {
Activity activity = mActivities.get(i);
if (activity.getClass().getCanonicalName().equals(targetClass.getCanonicalName())) {
mActivities.remove(activity);
activity.finish();
i--;
size--;
}
}
}
// 退出整個(gè)應(yīng)用
public void exitApp() {
int size = mActivities.size();
for (int i = 0; i < size; i++) {
Activity activity = mActivities.get(i);
Log.d(TAG, "exitApp canonicalName:" + activity.getClass().getCanonicalName());
mActivities.remove(i);
activity.finish();
i--;
size--;
}
}
// 獲取當(dāng)前的Activity,用于全局彈出Dialog
public Activity currentActivity() {
return mActivities.lastElement();
}
}
BaseActivity:
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityManager.getInstance().attach(this);
setContentView(getContentView());
}
protected abstract int getContentView();
@Override
protected void onDestroy() {
ActivityManager.getInstance().detach(this);
super.onDestroy();
}
}
A隧哮、B桶良、CMainActivity:
public class AMainActivity extends BaseActivity {
@Override
protected int getContentView() {
return R.layout.activity_amain;
}
public void click(View v) {
Intent intent = new Intent(this, BMainActivity.class);
startActivity(intent);
}
}
public class BMainActivity extends BaseActivity {
@Override
protected int getContentView() {
return R.layout.activity_bmain;
}
public void click(View v) {
Intent intent = new Intent(this, CMainActivity.class);
startActivity(intent);
}
}
public class CMainActivity extends BaseActivity {
@Override
protected int getContentView() {
return R.layout.activity_cmain;
}
public void click(View v) {
ActivityManager.getInstance().finish(this);
ActivityManager.getInstance().finish(BMainActivity.class);
}
public void exit(View v) {
ActivityManager.getInstance().exitApp();
}
}
上面的ActivityManager是一個(gè)單例,具有獲取當(dāng)前Activty沮翔、根據(jù)Activity對(duì)象或者Class刪除Activity陨帆、退出應(yīng)用等功能。
總結(jié):
單例常用實(shí)現(xiàn)方式:
1. 餓漢式——一般情況下都可以使用采蚀,最簡(jiǎn)單疲牵,最近本
2. 靜態(tài)內(nèi)部類的方式——需要演示加載的時(shí)候使用
3. 雙重檢查的鎖DCL——面試的時(shí)候會(huì)問(wèn),也是比較常見(jiàn)
4. 枚舉——高安全性的情景下使用
END.