在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    常用的設(shè)計(jì)模式-單例模式

    這是我的學(xué)習(xí)筆記,純手打,本想寫在紙質(zhì)筆記本上的,但時(shí)間一久就容易丟,所以還是記在網(wǎng)絡(luò)上吧:

    意圖:保證每個(gè)類,只有一個(gè)實(shí)例,并且提供一個(gè)全局的訪問點(diǎn)

    場景:需要嚴(yán)格的控制全局變量時(shí),如線程對(duì)象、數(shù)據(jù)庫連接池對(duì)象,不需要多個(gè)實(shí)例的對(duì)象,如工具類。

    雙重檢測加鎖的單例實(shí)現(xiàn):

    class LazySingleton{ private volatile static LazySingleton instance; private LazySingleton(){ } public static LazySingleton getInstance(){ if (null == instance){ synchronized(LazySingleton.class){ if(null == instance){ instance = new LazySingleton(); } } } return instance; }}

    這里用了synchronized,先擴(kuò)展一下筆記synchronized的四種用法

    >>>

    一、synchronized同步鎖為普通對(duì)象:

    public void function(){ synchronized (object) { //doSomething… }}

    當(dāng)某個(gè)線程要訪問如上代碼塊中的內(nèi)容時(shí),若該線程獲得object對(duì)象的鎖,那么就獲得了執(zhí)行權(quán),否則此線程被阻塞,直到其他線程釋放了object對(duì)象的鎖。

    舉個(gè)例子:

    public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(new Object()); // new了5個(gè)新對(duì)象到數(shù)組種 } for(Sync sync : syncs){ sync.start(); } }}class Sync extends Thread{ Object syncObj; public Sync(Object syncObj) { this.syncObj = syncObj; } public void run(){ synchronized (syncObj) { System.out.println(Thread.currentThread().getName()+"運(yùn)行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"結(jié)束…"); } }}

    運(yùn)行結(jié)果:

    Thread-0運(yùn)行中…Thread-1運(yùn)行中…Thread-2運(yùn)行中…Thread-3運(yùn)行中…Thread-4運(yùn)行中…Thread-1結(jié)束…Thread-0結(jié)束…Thread-3結(jié)束…Thread-4結(jié)束…Thread-2結(jié)束…

    看到這5個(gè)線程并沒有按順序執(zhí)行,他們之間不是同步的。這是因?yàn)檫@5個(gè)線程的syncObj并不是指向同一個(gè)對(duì)象,他們之間不存在同步鎖的競爭,所以是非同步的。將程序改為:

    public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; Object object = new Object(); //改了這里 for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(object); //數(shù)組里的對(duì)象都是同一個(gè),前面是5個(gè)不一樣的 } for(Sync sync : syncs){ sync.start(); } }}class Sync extends Thread{ Object syncObj; public Sync(Object syncObj) { this.syncObj = syncObj; } public void run(){ synchronized (syncObj) { System.out.println(Thread.currentThread().getName()+"運(yùn)行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"結(jié)束…"); } }}

    運(yùn)行結(jié)果:

    Thread-0運(yùn)行中…Thread-0結(jié)束…Thread-4運(yùn)行中…Thread-4結(jié)束…Thread-2運(yùn)行中…Thread-2結(jié)束…Thread-3運(yùn)行中…Thread-3結(jié)束…Thread-1運(yùn)行中…Thread-1結(jié)束…

    這5個(gè)線程都達(dá)到了效果,但是5個(gè)線程的執(zhí)行順序并不是固定的,這是編譯是重排序造成的。

    所以,若想要多個(gè)線程同步,則這些線程必須競爭同一個(gè)同步鎖。

    二、synchronized同步鎖為類

    類其實(shí)也是一個(gè)對(duì)象,可以去按照普通對(duì)象的方式去理解。不同之處在于,普通對(duì)象作用于某個(gè)實(shí)例,而類對(duì)象作用于整個(gè)類。

    上面的例子我改改:

    public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(); } for(Sync sync : syncs){ sync.start(); } }}class Sync extends Thread{ public void run(){ synchronized (SyncTest.class) { // 改了這里 System.out.println(Thread.currentThread().getName()+"運(yùn)行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"結(jié)束…"); } }}

    運(yùn)行結(jié)果:

    Thread-0運(yùn)行中…Thread-0結(jié)束…Thread-3運(yùn)行中…Thread-3結(jié)束…Thread-1運(yùn)行中…Thread-1結(jié)束…Thread-4運(yùn)行中…Thread-4結(jié)束…Thread-2運(yùn)行中…Thread-2結(jié)束…

    這些線程同樣實(shí)現(xiàn)了同步,因?yàn)樗麄兊耐芥i是同一個(gè)對(duì)象SyncTest類對(duì)象。

    需要注意的是,類對(duì)象鎖和普通對(duì)象鎖是不同的兩個(gè)鎖(即使這個(gè)對(duì)象是這個(gè)類的實(shí)例),他們之間互不干擾。

    public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(); } Sync1 sync1 = new Sync1(new SyncTest()); for(Sync sync : syncs){ sync.start(); //類對(duì)象鎖跑5個(gè)線程 } sync1.start(); //普通對(duì)象鎖跑一個(gè)線程 }}class Sync extends Thread{ public void run(){ synchronized (SyncTest.class) { //類對(duì)象鎖 System.out.println(Thread.currentThread().getName()+"運(yùn)行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"結(jié)束…"); } }} class Sync1 extends Thread{ SyncTest syncTest; public Sync1(SyncTest syncTest){ this.syncTest = syncTest; } public void run(){ synchronized (syncTest) { //普通對(duì)象鎖 System.out.println(Thread.currentThread().getName()+"運(yùn)行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"結(jié)束…"); } }}

    運(yùn)行結(jié)果:

    Thread-0運(yùn)行中…Thread-5運(yùn)行中…Thread-0結(jié)束…Thread-5結(jié)束…Thread-4運(yùn)行中…Thread-4結(jié)束…Thread-2運(yùn)行中…Thread-2結(jié)束…Thread-3運(yùn)行中…Thread-3結(jié)束…Thread-1運(yùn)行中…Thread-1結(jié)束…

    可以看到,雖然Sync1中的對(duì)象鎖是SyncTest的實(shí)例,但是Sync1與Sync的run方法中的synchronized代碼塊并沒有實(shí)現(xiàn)同步,他們可以同時(shí)訪問這段代碼。驗(yàn)證了互相不干擾。

    三、synchronized修飾普通方法

    synchronized修飾方法在本質(zhì)上和修飾代碼塊是一樣的,他們都是通過同步鎖來實(shí)現(xiàn)同步的。

    public synchronized void syncFunction(){ //doSomething…}

    synchronized修飾普通方法中的同步鎖就是這個(gè)對(duì)象本身,即 this

    看代碼:

    class Sync extends Thread{ public synchronized void syncFunction(){ doSomething(); } //syncFunction() 和 syncFunction2() 實(shí)現(xiàn)的同步效果是一樣的。 public void syncFunction2(){ synchronized (this) { doSomething(); } } private void doSomething(){ //doSomething… }}

    當(dāng)類中某個(gè)方法test()被synchronized關(guān)鍵字所修飾時(shí),所有不同的線程訪問這個(gè)類的同一個(gè)實(shí)例的test()方法都會(huì)實(shí)現(xiàn)同步的效果。不同的實(shí)例之間不存在同步鎖的競爭,也就是說,不同的線程訪問這個(gè)類不同實(shí)例的test()方法并不會(huì)實(shí)現(xiàn)同步。這很容易理解,因?yàn)椴煌膶?shí)例同步鎖不同,每個(gè)實(shí)例都有自己的”this”。

    四、synchronized修飾靜態(tài)方法

    public static synchronized void syncFunction(){ //doSomething…}

    同樣的,synchronized作用于靜態(tài)方法時(shí),跟使用類對(duì)象作為靜態(tài)鎖的效果是一樣的,此時(shí)的類對(duì)象就是靜態(tài)方法所屬的類。

    不同的線程訪問某個(gè)類不同實(shí)例的syncFunction()方法(被synchronized修飾的靜態(tài)方法,如上)時(shí),他們之間實(shí)現(xiàn)了同步效果。結(jié)合上面的解釋,這種情況也很好理解:此時(shí)不同線程競爭同一把同步鎖,這就是這個(gè)類的類對(duì)象鎖。

    總結(jié)

    理解synchronized的關(guān)鍵就在于:若想要多個(gè)線程同步,則這些線程必須競爭同一個(gè)同步鎖。這個(gè)同步鎖,可以理解為一個(gè)對(duì)象。

    >>>

    擴(kuò)展完畢,繼續(xù)說單例模式

    我們知道,創(chuàng)建一個(gè)對(duì)象大致分為三步:

    step1 開辟空間

    step2 初始化空間

    step3 賦值

    但是編譯器、即時(shí)編譯,CPU可能對(duì)字節(jié)碼進(jìn)行優(yōu)化,對(duì)編譯后的字節(jié)碼進(jìn)行指令的重新排序。比如將第2步和第3步置換位置、那就會(huì)出現(xiàn)有一個(gè)狀況:

    線程A賦值完畢,但未完成初始化時(shí),線程B在走第一個(gè)IF檢查時(shí),發(fā)現(xiàn)instance實(shí)例不為null,直接return未初始化的對(duì)象,出現(xiàn)異常。

    加入volatile關(guān)鍵字給實(shí)例變量,是為了防止JVM進(jìn)行指令重排而出現(xiàn)的問題。

    private volatile static LazySingleton instance;

    上述時(shí)懶漢式實(shí)現(xiàn)單例,意為在需要時(shí)才去實(shí)例化。

    那么對(duì)應(yīng)的有餓漢式:

    class HungrySingleton{ //靜態(tài)屬性public static String name=”JJ”;private static HungrySingleton instance = new HungrySingleton(); //構(gòu)造私有化private HungrySingleton(){ } //獲取實(shí)例的方法public static HungrySingleton getInstance(){ return instance; }}

    只有在主動(dòng)使用對(duì)應(yīng)的類時(shí),才會(huì)觸發(fā)實(shí)例的初始化,如當(dāng)前類是啟動(dòng)類即main函數(shù)所在的類,直接進(jìn)行new操作,訪問靜態(tài)屬性、訪問靜態(tài)方法、用反射訪問、初始化一個(gè)類的子類等,都會(huì)觸發(fā)實(shí)例的初始化操作。

    加載的初始化階段就完成了實(shí)例的初始化。本質(zhì)上是借助了JVM類加載機(jī)制,保證實(shí)例的唯一性(初始化過程只會(huì)執(zhí)行一次)和線程安全(JVM以同步的形式來完成類加載的整個(gè)過程)。

    類加載過程:

    1.加載二進(jìn)制數(shù)據(jù)到內(nèi)存中,生成對(duì)應(yīng)的Class數(shù)據(jù)結(jié)構(gòu)

    2.連接:驗(yàn)證是否符合規(guī)范,準(zhǔn)備給類的靜態(tài)成員變量賦默認(rèn)值 ,解析

    3.初始化:給類的靜態(tài)變量賦初值

    JDK中的Runtime類就是使用了餓漢式。

    如果通過反射來獲取實(shí)例對(duì)象呢?是否和getInstance()獲取的實(shí)例對(duì)象是同一個(gè)?

    答案:不是

    反射獲?。?/p>

    //通過構(gòu)造函數(shù)實(shí)現(xiàn)Constructor declaredConstructor = HungrySingleton.class.getDeclaredConstractor();//訪問控制權(quán)設(shè)置為truedeclaredConstructor.setAccessible( true );//通過反射實(shí)例化HungrySingleton hungrySingleton = declaredConstructor.new Instance();//通過餓漢式實(shí)例化HungrySingleton instance = HungrySingleton.getInstance();//會(huì)輸出falseSystem.out.print(hungrySingleton == instance );

    為了避免私有構(gòu)造被反射暴力使用的問題,可以在私有構(gòu)造方法中加多一個(gè)判斷instance是否為null,如果不為null,則拋出RuntimeException異常。

    內(nèi)部類單例模式:

    class InnerClassSingleton{private static InnerClassSingleton instance = new InnerClassSingleton(); //構(gòu)造方法私有化private InnerClassSingleton(){}//獲取實(shí)例的方法public static InnerClassSingleton getInstance(){ //返回的還是InnerClassSingleton的實(shí)例 return SingletonHolder.instance; }//靜態(tài)內(nèi)部類private static class SingletonHolder{private static InnerClassSingleton instance = new InnerClassSingleton();}}

    相對(duì)于餓漢式,該方式只在調(diào)用getInstance()方法時(shí)才會(huì)初始化。是餓漢模式和懶漢模式的結(jié)合。

    枚舉單例模式:

    enmu EnmuSingleton{INSTANCE; public void print(){ System.out.println(“xxx”); }}

    在JVM中生成的INSTANCE實(shí)例類型是static final類型的。可以通過calss文件反編譯看到。

    另外,還可以通過反序列化的方式來獲取另一個(gè)單例,這兩個(gè)“單例”是不相同的,這就相當(dāng)于對(duì)單例模式進(jìn)行了破壞。

    解決辦法:實(shí)現(xiàn)序列化接口,指定serialVersionUID的值。這樣就保證了實(shí)例被序列化,反序列化是的版本都是一樣的。否則會(huì)報(bào)異常。

    還有些類使用到了雙重檢測單例:

    比如:Spring中的ReactiveAdapterRegistry、tomcat中的TomcatURLStreanHandlerFactory.

    OK,單例模式差不多就先學(xué)到這里了。

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
    上一篇 2022年6月27日 09:17
    下一篇 2022年6月27日 09:17

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息