1. 程式人生 > >Java 執行緒安全問題及執行緒鎖(讀書筆記)

Java 執行緒安全問題及執行緒鎖(讀書筆記)

多執行緒安全問題:

首先整理多執行緒同步的知識點,開頭肯定是要先探討探討多執行緒安全的問題。那麼嘛叫執行緒安全問題呢?
答: 我們知道Jvm虛擬機器的設計中執行緒的執行是搶佔式的,執行緒的執行時間是由底層系統決定的。所以就會有多個執行緒修改同一個資料時不同步問題。就叫多執行緒安全問題。

我們用一個例子來實際探討一下這種情況!比如現在我們有一個銀行賬號,裡面有1000元錢。然後有兩個使用者要取錢,我們啟動兩個執行緒去訪問去取錢,每個取800。 按照邏輯來說的話,應該是第一個取出來800,第二個取錢的時候提示餘額不足。

下面我們來看一下程式碼,首先是Account類:

package com.example.thread;
/**
 * auther: Simon zhang
 * Emaill:
[email protected]
*/
public class Account { private String accountNO; private double balance; public Account(){ } public Account(String accountNO, double balance) { this.accountNO = accountNO; this.balance = balance; } public String getAccountNO() { return
accountNO; } public void setAccountNO(String accountNO) { this.accountNO = accountNO; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } @Override public int hashCode(){ return
accountNO.hashCode(); } @Override public boolean equals(Object obj) { if(this==obj) return true; if(obj!=null&&obj.getClass()==Account.class){ Account account= (Account) obj; return account.getAccountNO().equals(accountNO); } return false; } }

然後是取錢執行緒:

package com.example.thread;

/**
 * auther: Simon zhang
 * Emaill:[email protected]
 */
public class DrawThread  extends Thread{
    //取錢的使用者
    private Account account;

    //當前執行緒所希望取的錢
    private double drawAmount;

    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }

    @Override
    public void run() {
        if(account.getBalance()>=drawAmount){
            System.out.println(getName()+"取錢成功! 取出="+drawAmount);
         /*   try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            //修改金額
            account.setBalance( (account.getBalance()-drawAmount));
            System.out.println("\t 餘額為:"+account.getBalance());
        }else{
            System.out.println(getName()+"取錢失敗,賬號餘額不足!");
        }
    }
}

最後是測試程式碼:

public class Test {
    public static void main(String args[]) throws Exception {
            Account account=new Account("123456",1000);
            DrawThread a = new DrawThread("A", account, 800);
            a.start();
            DrawThread b =  new DrawThread("B",account,800);
            b.start();
    }
}

多執行幾次,看一下控制檯的輸出。竟然出現一個讓人大跌眼鏡的結果。

     A取錢成功! 取出=800.0
     B取錢成功! 取出=800.0
     餘額為:-600.0
     餘額為:200.0

這尼瑪什麼情況,怎麼能取出兩個800呢。這銀行還不得賠死呀。 ok. 說回正題,我們來分析一下這種結果是怎麼產生的,假設第一個執行緒執行完成 account.getBalance()>=drawAmount 這行程式碼後,cpu將它停止,切換到第二執行緒去執行account.getBalance()>=drawAmount。這個時候記憶體中的account還是1000, 因為第一個執行緒沒有執行完成account.setBalance( (account.getBalance()-drawAmount))去修改account值。所以第二個執行緒繼續還是進入了取錢的程式碼塊。這個時候其實邏輯上已經出錯了,所以當前兩個執行緒都減去800的時候就會出現負值。這就是多執行緒的安全問題。

既然發現了這個問題,我們就應該要面對它,解決它。那怎麼解決呢,其實我們只要想想如果有一種方式可以保證一個執行緒進入run方法後,直到它修改完account退出。其他執行緒都不能進入run不就可以了。Java語言的設計者也是這麼想的,而且他們想了很多的方式來解決這個問題。

  • 同步程式碼塊
  • 同步方法
  • 同步鎖

下面我們一個一個來梳理一下它們!

同步程式碼塊

語法格式:

 synchronized(obj){
   ...
   //此處程式碼就是同步程式碼
 }  

上面的語法格式中的synchronized後括號裡面的就是同步監視器,上面程式碼的含義是:執行緒開始執行同步程式碼塊之前,必須獲得對同步監視器的鎖定。
那麼使用同步程式碼塊的話,我們可以把取錢的程式碼修改成如下格式!

package com.example.thread;

/**
 * auther: Simon zhang
 * Emaill:[email protected]
 * 同步程式碼塊
 */
public class SyncCodeDrawThread extends Thread{

    //取錢的使用者
    private Account account;

    //當前執行緒所希望取的錢
    private double drawAmount;

    public SyncCodeDrawThread(String name, Account account, double drawAmount){
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }

    @Override
    public void run() {
        synchronized (account){
            if(account.getBalance()>=drawAmount){
                System.out.println(getName()+"取錢成功! 取出="+drawAmount);
             /*   try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                //修改金額
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t 餘額為:"+account.getBalance());
            }else{
                System.out.println(getName()+"取錢失敗,賬號餘額不足!");
            }
        }
//        System.out.println("測試同步程式碼塊後面的程式碼能否執行!");
    }
}

多次執行後,程式碼都是正確執行的。 說明我們做對了。慶祝一下!

同步程式碼塊使用時的注意事項:
1.我們一定要保證使用的同步監視器物件對於呼叫這個方法的執行緒是唯一的。怎麼理解這句話呢?
比如上面的程式碼account物件對於兩個取錢執行緒就是唯一的,因為它是同一個物件。 如果我們把synchronized (account)改成synchronized (this),多次執行程式碼後,我們發現還是得到的錯誤的結果。因為this對於的是取錢執行緒本身,每個執行緒都不一樣。 其實我們可以把同步監視器理解成一個門,要鎖定的程式碼理解成門後的東西。我們要禁止其他人通過門去訪問門後的東西,那就要把門鎖住,而且要保證只有一個門,要不然別人從別的門中直接就去訪問了。 是不是呢!!

2.任何時刻只能有一個執行緒可以獲得對同步監視器的鎖定,當同步程式碼塊執行完成後,該執行緒會釋放對同步監視器的鎖定。

3.有時候我們會在一個方法中,用同步程式碼塊只鎖定住一段修改資源的程式碼,而在同步程式碼塊之後還有一些其他程式碼。那麼我們有執行緒鎖定同步程式碼塊後,其他執行緒可以執行同步程式碼塊後面的東西嗎?

答案是:不行,親測!

    @Override
    public void run() {
        synchronized (account){
            if(account.getBalance()>=drawAmount){
                System.out.println(getName()+"取錢成功! 取出="+drawAmount);
             /*   try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                //修改金額
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t 餘額為:"+account.getBalance());
            }else{
                System.out.println(getName()+"取錢失敗,賬號餘額不足!");
            }
        }
        System.out.println(getName()+"測試同步程式碼塊後面的程式碼能否執行!");
    }

同步方法:

同樣的我們也可以使用同步方法來限定多個執行緒訪問同一個資源的問題,同步方法就是使用synchronized關鍵字來修飾某一個方法,則該方法稱為同步方法。對於同步方法而言,無需顯示的指定同步監視器,同步方法的同步監視器就是this,也就是該物件本身。
那如果用同步方法,我們要怎麼上面的取錢修改程式碼呢?
先修改Account類,為它新增取錢的方法draw.

public synchronized  void  draw(String name,double drawAmount){
        if(balance>=drawAmount){
            System.out.println(name+"取錢成功! 取出="+drawAmount);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //修改金額
            balance=balance-drawAmount;
            System.out.println("\t 餘額為:"+balance);
        }else{
            System.out.println(name+"取錢失敗,賬號餘額不足!");
        }
}

再修改Thread,在run方法中呼叫account的run方法去取錢!

  @Override
    public void run() {
        account.draw(getName(),drawAmount);
    }

為什麼要這麼修改呢,還是老話題,我們要保證同步鎖物件對於兩個呼叫的執行緒是唯一的,而現在同步方法的同步鎖物件預設是this物件。那只有account物件對於這兩執行緒是唯一的。所以通過給account新增一個同步方法draw來同步限定。

同步方法使用時的注意事項:
1.例項方法預設的同步鎖物件是this,而靜態方面預設的同步鎖物件是類物件。
2.同步方法預設鎖定的是所有添加了相關同步鎖的物件。怎麼理解這句話呢?
比如下面account物件有兩個方法draw,otherDraw都是例項方法,默認同步鎖是this。 那麼如果有一個執行緒鎖定了draw方法,那麼其他執行緒也就不能訪問otherDraw方法了。需要等地一個執行緒釋放了鎖後,才能訪問。

  /**
     * 鎖如果沒有被釋放,那其他地方就不能呼叫這個鎖鎖定的程式碼
     */
    public synchronized  void  draw(String name,double drawAmount){
            if(balance>=drawAmount){
                System.out.println(name+"取錢成功! 取出="+drawAmount);
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改金額
                balance=balance-drawAmount;
                System.out.println("\t 餘額為:"+balance);
            }else{
                System.out.println(name+"取錢失敗,賬號餘額不足!");
            }
    }

    /**
     *  如果有其他的同步鎖沒有被釋放,那就不能訪問這個方法
     */
    public synchronized void otherDraw(String name){
        System.out.println("----adraw-----");
    }

同步鎖(Lock)

Java1.5開始,Java提供了一種功能更強大的執行緒同步機制—-通過顯示定義同步鎖物件來實現同步,在這種機制下,同步鎖使用Lock物件充當。
Lock有很多種類,某些鎖可能允許對共享資源併發訪問,如ReadWriteLock(讀寫鎖),Lock和ReadWriteLock是java 5新提供的兩個根介面,併為Lock提供了ReentrantLock(可重入鎖)實現類,為ReadWriteLock提供了ReentrantReadWriteLock實現類。

我們使用ReentrantLock來修改取錢的程式碼,新增同步的限制:

      private  final ReentrantLock look=new ReentrantLock();

      public  void  draw(String name,double drawAmount){
         //加鎖
          look.lock();
         try {
            if(balance>=drawAmount){
                System.out.println(name+"取錢成功! 取出="+drawAmount);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改金額
                balance=balance-drawAmount;
                System.out.println("\t 餘額為:"+balance);
            }else{
                System.out.println(name+"取錢失敗,賬號餘額不足!");
            }
        }finally {
            //釋放鎖
            look.unlock();
        }
    }

上面程式中的第一行程式碼定義了一個ReentrantLock物件,程式中實現draw()方法時,進入方法執行後立即請求對ReentrantLock物件進行加鎖,當執行完draw()方法的取錢邏輯之後,程式使用finally塊來確保鎖的釋放。

注意: 同步方法或同步程式碼塊使用與競爭資源相關的,隱式的同步監視器,並且強制要求加鎖和釋放鎖要出現在一個塊結構中,而且當獲取到多個鎖時,它們必須以相反的順序釋放,且必須在與所有鎖被釋放時相同的範圍內釋放所有鎖。

同步鎖(Lock)使用的注意事項:
1. Lock提供了同步方法和同步程式碼塊所沒有的其他功能,包括用於非塊結構的tryLock()方法,以及試圖獲取可中斷鎖的lockInterruptibly方法,還有獲取超時失效鎖tryLock(long,TimeUnit); 方法。
2. ReentrantLock鎖具有可重入性,也是說,一個執行緒可以對已被加鎖的ReentrantLock鎖再次加鎖,ReentrantLock物件會維持一個計數器來追蹤lock()方法的巢狀呼叫,執行緒在每次呼叫lock()加鎖後,必須顯示呼叫unlock()釋放鎖,所以一段被鎖保護的程式碼可以呼叫另一個被相同鎖保護的方法。

釋放執行緒鎖的鎖定

注:這裡說的執行緒鎖的鎖定,只指的是同步程式碼塊和同步方法,Lock需要顯示釋放鎖定所以不在討論範圍之內。
任何執行緒在進入同步程式碼塊,同步方法之前,必須先獲得對同步程式碼塊的鎖定,那麼何時會釋放同步監視器的鎖定呢?

程式無法顯示的釋放同步監視器的鎖定。執行緒會在如下幾種情況下釋放對同步監視器的鎖定:

  • 當前執行緒的同步方法,同步程式碼塊執行結束,當前執行緒即釋放同步監視器。
  • 當前執行緒在同步程式碼塊,同步方法中遇到break,return終止了該程式碼塊或該方法的繼續執行,當前執行緒會釋放同步監視器。
  • 當前執行緒在同步程式碼塊,同步方法中出現了未處理的Error或Excepation,導致了該程式碼塊,該方法異常結束時,當前執行緒會釋放同步鎖。
  • 當前執行緒執行同步程式碼或同步方法時,程式執行了同步監視器物件的wait方法,則當前執行緒暫停,並釋放同步監視器。

在如下情況下,執行緒不會釋放同步鎖:

  • 執行緒執行同步程式碼塊或同步方法時,程式呼叫了Thread.sleep(),Thread.yield()方法來暫停當前執行緒的執行,當前執行緒不會釋放同步監視器。
  • 程式執行同步程式碼塊時,其他執行緒執行了該執行緒的suspend()方法將該執行緒掛起,該執行緒不會釋放同步監視器。

    死鎖

    最後討論一下這個牛逼的概念以及它是怎麼產生的。死鎖是個啥?當兩個執行緒相互等待對方釋放同步監視器時就會發生死鎖,Java虛擬機器沒有檢測,也沒有采取措施處理死鎖這種情況,所以多執行緒程式設計時應該採取措施避免死鎖出現。一旦出現死鎖,整個程式即不會發生任何異常,也不會給出任何提示,只是所有執行緒處於阻塞狀態,無法繼續。
    下面用程式碼來實現一下死鎖,哈哈!

package com.example.thread;

/**
 * auther: Simon zhang
 * Emaill:[email protected]
 */

public class DeadLock implements  Runnable {

    class A{
        public synchronized void foo(B b){

          System.out.println("當前執行緒名:"+Thread.currentThread().getName()+"進入了A例項的foo方法");//①

          try {
                Thread.sleep(200);
          }catch (InterruptedException e){
            e.printStackTrace();
          }

          System.out.println("當前執行緒名:"+Thread.currentThread().getName()+"企圖呼叫B例項的last方法");//③
          b.last();
        }

        public synchronized void last(){
            System.out.println("進入了A例項的last方法");
        }
    }

    class B{
      public synchronized void bar(A a){
          System.out.println("當前執行緒名:"+Thread.currentThread().getName()+"進入了B例項的bar方法");//②

          try {
              Thread.sleep(200);
          }catch (InterruptedException e){
              e.printStackTrace();
          }
          System.out.println("當前執行緒名:"+Thread.currentThread().getName()+"企圖呼叫A例項的last方法");//④
          a.last();
      }

      public synchronized void last(){
          System.out.println("進入了B例項的last方法");
      }
    }

    A a=new A();
    B b=new B();

    public void init(){
      Thread.currentThread().setName("主執行緒");
       a.foo(b);
      System.out.println("進入了主執行緒之後");
    }

    @Override
    public void run() {
        Thread.currentThread().setName("子執行緒");
        b.bar(a);
        System.out.println("進入了子執行緒之後");
    }

    public static void main(String args[]) throws Exception {
       DeadLock dl=new DeadLock();
       new Thread(dl).start();
        dl.init();
    }
}

執行上面的程式:

當前執行緒名:主執行緒進入了A例項的foo方法
當前執行緒名:子執行緒進入了B例項的bar方法
當前執行緒名:主執行緒企圖呼叫B例項的last方法
當前執行緒名:子執行緒企圖呼叫A例項的last方法

死鎖分析,從執行日誌可以看出,程式即無法向下執行,也不會丟擲任何異常,就這樣一直僵持著。究其原因,是因為:上面的程式中A物件和B物件的方法都是同步方法,也就是說A物件和B物件都是同步鎖。程式中的兩個執行緒執行,一個執行緒的執行緒執行體是DeadLock的run方法,另一個執行緒的執行體是DeadLock的init方法(主執行緒呼叫了init()方法)。其中run()方法中讓B物件呼叫bar()方法,而init()方法讓A物件呼叫foo()方法。從輸出可以看出init方法先執行,呼叫了A物件的foo方法,進入foo()方法之前,該執行緒對A物件進行加鎖,—–當程式執行到①號程式碼時,主執行緒暫停200ms: CPU切換到執行另一個執行緒,讓B物件執行bar()方法,所以看到子執行緒開始執行B例項的bar方法,進入bar方法之前,該執行緒對B物件加鎖—–當程式執行到②號程式碼的時候,子執行緒也暫停200ms;接下來主線會先醒過來,繼續向下執行,直到③號程式碼處希望呼叫B物件的last方法—-執行前必須對B物件進行加鎖,但此時子執行緒正保持著對B物件的鎖,所以主執行緒阻塞;接下來子執行緒也醒過來了,繼續向下執行,直到④號程式碼處希望呼叫A物件的last()方法—–執行該方法之前必須對A物件進行加鎖,但此時主執行緒沒有釋放A物件的鎖。 到這裡就出現主執行緒保持A物件的鎖,等待對B物件加鎖,而子執行緒保持著B物件的鎖,等待對A物件加鎖,兩個執行緒互相等待對方先釋放,所以就出現了死鎖。

哈哈哈,,,真尼瑪 累死。。

ok. 到這裡,執行緒安全問題和執行緒鎖的分析就告一段落了。。。 不容易呀!!!!

相關推薦

Java 執行安全問題執行讀書筆記

多執行緒安全問題: 首先整理多執行緒同步的知識點,開頭肯定是要先探討探討多執行緒安全的問題。那麼嘛叫執行緒安全問題呢? 答: 我們知道Jvm虛擬機器的設計中執行緒的執行是搶佔式的,執行緒的執行時間是由底層系統決定的。所以就會有多個執行緒修改同一個資料時不同

java執行平行計算之矩陣乘法星星筆記

用java編寫兩個n階的方陣A和B的相乘程式,結果存放在方陣C中,其中使用Runnable介面實現矩陣的乘法。 方陣A和B的初始值如下:(同時開兩個執行緒) 輸出:c中所有元素之和、程式的執行時間 具體的程式如下: package com.xing.matrix; /

java多線程中的死情況讀書筆記

bubuko 實例 syn 釋放 splay inf info sys 資源 多線程中的死鎖 在前面的分析中,我們知道一個對象可以用Synchronized方法或者其他的加鎖形式來防止別的任務在互斥還沒有釋放的時候就訪問這個對象。 試想一下這樣的情況:某個任務在等待另一個任

2.3 如何得到真實的執行計劃 《基於Oracle的優化學習筆記

  if( hash_values.count > 0 ) then     for i in hash_values.first .. hash_values.last loop            dbms_output.put_line('----------------------------

2.5 Oracle裡常見執行計劃 《基於Oracle的優化學習筆記

與表訪問相關的執行計劃 全表掃描的關鍵字是:TABLE ACESS FULL ROWID掃描的關鍵字:TABLE ACESS BY USER ROWID 或 TABLE ACESS BY INDEX ROWID 與B樹相關的執行計劃 索引唯一掃描:INDEX UN

金三銀四:螞蟻金服JAVA開發面試題答案之一面持續更新

開發十年,就只剩下這套架構體系了! >>>   

java中的try-catch-finnal異常處理學習筆記

不堪 java sha highlight 抽取 最終 throwable 關鍵字 學習筆記 一、異常概述 異常:Exception,是在運行發生的不正常情況。 原始異常處理: if(條件) {   處理辦法1  處理辦法2  處理辦法3} if(條件) {   處理辦法

深入理解Java虛擬機器——垃圾收集器與記憶體分配策略讀書筆記

判斷物件是否存活 1、引用計數法 給物件新增一個引用計數器,每當有一個地方引用它時,計數器值加1,當引用失效時,計數器值減1, 任何時刻計數器為0的物件就是不可能再被使用的。 缺點:不能解決物件之間迴圈引用的問題 2、根搜尋演算法(GC Roots Tracing)

Java記憶體回收知識讀書筆記--深入理解Java虛擬機器——JVM高階特性與最佳實踐(第2版)2.2~2.3

1.哪些地方的記憶體要回收? Java程式運時的記憶體包括以下幾部分:程式計數器,Java虛擬機器棧,本地方法棧,Java堆,方法區(執行時常量池是方法區的一部分)。 程式計數器,Java虛擬機器棧,本地方法棧是隨執行緒而生,隨執行緒而亡,它們的分配的記憶體大小已知,因此不

Java執行-----執行安全解決機制

   1.什麼是執行緒安全問題?      從某個執行緒開始訪問到訪問結束的整個過程,如果有一個訪問物件被其他執行緒修改,那麼對於當前執行緒而言就發生了執行緒安全問題; 如果在整個訪問過程中,無一物件被其他執行緒修改,就是執行緒安全的,即存在兩個或者兩個以

java執行 執行安全執行安全的集合物件

一、概念: 執行緒安全:就是當多執行緒訪問時,採用了加鎖的機制;即當一個執行緒訪問該類的某個資料時,會對這個資料進行保護,其他執行緒不能對其訪問,直到該執行緒讀取完之後,其他執行緒才可以使用。防止出現數據不一致或者資料被汙染的情況。 執行緒不安全:就是不提供資料訪問時的資

java執行安全執行執行通訊快速入門

一:多執行緒安全問題 ###1 引入 /* * 多執行緒併發訪問同一個資料資源 * 3個執行緒,對一個票資源,出售 */ public class ThreadDemo { public static void main(String[

Java併發程式設計之執行安全執行通訊

Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程

JVMTI 中的JNI系列函式,執行安全除錯技巧

JVMTI 中的JNI系列函式,執行緒安全及除錯技巧 jni functions 在使用 JVMTI 的過程中,有一大系列的函式是在 JVMTI 的文件中 沒有提及的,但在實際使用卻是非常有用的。這就是 jni functions.

day 34 GIL執行佇列,執行執行池回撥函式

一 . GIL鎖   GIL鎖是python程式碼轉直譯器程式碼的一個鎖         雖然我們加鎖的原因是因為要保護安全性從而降低了效率,但是加鎖也會出現安全性的問題!         二 . 執行緒佇列   import queue   三種佇列形式

關於SpringMVC攔截器是否執行安全執行訪問產生的request和session

Springmvc的攔截器預設是執行緒不安全,即全域性屬性就是共享的即不執行緒安全(如下程式碼中變數i就是執行緒不安全的)。 定義了一個攔截器: 並配置了一個SessionListener(關於JavaWeb的Listener配置這裡沒貼程式碼): 設定sess

高併發下map和chan實現的連結池的執行安全效率

1.背景 上一次blog寫著寫著崩掉了,這次一定寫完一節儲存一節。 目前從事go語言的後臺開發,在叢集通訊時需要用到thrift的rpc。由於叢集間通訊非常頻繁且併發需求很高,所以只能採用連線池的形式。由於叢集規模是有限的,每個節點都需要儲存平行節點的連線,所以

Java執行 —— 執行安全執行同步、執行間通訊含面試題集

上一篇博文:Java多執行緒(一) —— 執行緒的狀態詳解中詳細介紹了執行緒的五種狀態及狀態間的轉換。本文著重介紹了執行緒安全的相關知識點,包括執行緒同步和鎖機制、執行緒間通訊以及相關面試題的總結 一、執行緒安全 多個執行緒在執行同一段程式碼的時候,每次的執行結果和單執行緒執行的結果都是一樣的,不存在執行結果

Java--辨別執行安全執行安全

在學習Java的時候經常會發現有很多名稱相似的類,比如HashMap和Hashtable,StringBuffer和StringBuilder等等,他們的名稱相似,功能也有相似的地方,所以初學者在學習之中往往會很疑惑,他們都有哪些不同呢?而在深入研究這個問題的時

列舉實現單例原理:執行安全發序列化依舊為單例原因

單例的列舉實現在《Effective Java》中有提到,因為其功能完整、使用簡潔、無償地提供了序列化機制、在面對複雜的序列化或者反射攻擊時仍然可以絕對防止多次例項化等優點,單元素的列舉型別被作者認為是實現Singleton的最佳方法。 其實現非常簡單,如下: