多線程、單例模式的學習

第三十二章 多線程、單例模式


提綱

  • 32.1 多線程
    • 32.1.1 什麼是並行和併發
    • 32.1.2 什麼是多進程
    • 31.1.3 什麼是多線程
    • 31.1.4 實現線程的兩種方式
    • 31.1.5 線程同步
    • 31.1.6 線程的生命週期
  • 32.2 單例模式
    • 32.2.1 什麼是單例模式
    • 32.2.2 爲什麼使用單例模式
    • 32.2.3 單例模式的特點
    • 32.2.4 單例模式的寫法
    • 32.2.5 總結

32.1 多線程

  • 32.1.1 什麼是並行和併發

    • 並行:指在某一個時間段內同時運行多個程序。
    • 併發:指在某一個時間點同時運行多個程序。
  • 32.1.2 什麼是多進程:以Windows系統爲例,Windows操作系統是多任務操作系統,它以進程爲單位。系統可以分配給每個進程一段有限的使用CPU的時間(也可以稱爲CPU時間片),CPU在這段時間中執行某個進程,然後下一個時間片又跳至另一個進程中去執行。由於CPU轉換較快,所以使得每個進程好像是同時執行一樣。所以,Windows系統中,進程是並行的,即在某一個時間點只能執行一個進程。

  • 31.1.3 什麼是多線程:一個線程是進程中的執行流程,一個進程可以同時包含多個線程,多個線程共享一個進程的資源。每個線程也可以得到一小段程序的執行時間,但是多線程也是並行而不是併發的,所以一個時間點也只能運行一個線程。

  • 31.1.4 實現線程的兩種方式:繼承java.lang.Thread類與實現java.lang.Runnable接口。

    • 繼承java.lang.Thread類
      1. 構造方法:
        • Thread():分配新的 Thread 對象。
        • Thread(String name):創建一個名爲name的線程對象。
        • Thread(Runnable target):通過Runnable接口分配新的 Thread 對象。
        • Thread(Runnable target, String name):通過Runnable接口創建一個名爲name的線程對象。
      2. 常用方法:
        • run():如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。返回值:void。
        • start():使該線程開始執行;Java 虛擬機調用該線程的 run 方法。返回值:void。
        • sleep(long millis):在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。返回值:static void。
        • join():等待該線程終止。返回值:void。
        • join(long millis):等待該線程終止的時間最長爲 millis 毫秒。返回值:void。
        • interrupt():中斷線程。返回值:void。
        • interrupted() :測試當前線程是否已經中斷。返回值:static boolean。
        • currentThread() :返回對當前正在執行的線程對象的引用。返回值:static Thread。
        • getId():返回該線程的標識符。返回值:long。
        • getName():返回該線程的名稱。返回值:String。
        • getPriority():返回線程的優先級。返回值:int。
        • setName(String name):改變線程名稱,使之與參數 name 相同。返回值:void。
        • setPriority(int newPriority):更改線程的優先級。返回值:void。
        • notify():喚醒在此對象監視器上等待的單個線程。返回值:void。
        • notifyAll(): 喚醒在此對象監視器上等待的所有線程。返回值:void。
        • wait():在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致當前線程等待。返回值:void。
        • wait(long timeout):在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,導致當前線程等待。返回值:void。
      3. 字段摘要
        • MAX_PRIORITY:線程可以具有的最高優先級。變量類型:static int。
        • MIN_PRIORITY:線程可以具有的最低優先級。變量類型:static int。
        • NORM_PRIORITY:分配給線程的默認優先級。變量類型:static int。
      4. 使用步驟舉例
        • 實現步驟:

          1. 繼承Thread 類
          2. 重寫run方法 在run方法寫上自己想執行的代碼 因爲線程執行的代碼都在run方法裏面
          3. 創建線程類對象,
            • 這裏是(MyThread mt = new MyThread();)
            • 然後將mt交給Thread(Thread tr = new Thread(mt);)
          4. 啓動線程
        • 實例:

            //創建線程
            public class MyThread extends Thread{
            	public void run() {
            		Thread th = Thread.currentThread();//獲取當前線程
            		//Thread.currentThread().getName()獲取當前線程的名字
            		System.out.println("當前線程的名字:"+th.getName());
            		th.setName("MyThread的線程");//設置當前線程的名稱
            		System.out.println("設置線程名字後:"+th.getName());
            		System.out.println("線程的標識符:"+th.getId());
            		System.out.println("線程的優先級:"+th.getPriority());
            		System.out.println("最高優先級:"+Thread.MAX_PRIORITY);
            		System.out.println("最低優先級:"+Thread.MIN_PRIORITY);
            		System.out.println("默認優先級:"+Thread.NORM_PRIORITY);
            		for (int i = 0; i < 10; i++) {
            			try {
            				Thread.sleep(500);//線程等待,參數傳毫秒數
            				System.out.print(i+" ");
            			} catch (InterruptedException e) {
            				e.printStackTrace();
            			}
            		}
            	}
            }
            //測試線程
            public static void main(String[] args) {
            	MyThread mt = new MyThread();
            	mt.start();//啓動線程
            }
            //執行結果
            當前線程的名字:Thread-0
            設置線程名字後:MyThread的線程
            線程的標識符:11
            線程的優先級:5
            最高優先級:10
            最低優先級:1
            默認優先級:5
            0 1 2 3 4 5 6 7 8 9 
          
    • 實現java.lang.Runnable接口
      1. 裏面只有一個方法:run():使用實現接口 Runnable 的對象創建一個線程時,啓動該線程將導致在獨立執行的線程中調用對象的 run 方法。

      2. 使用步驟:

        1. 實現Runnable接口
        2. 重寫run方法
        3. 創建線程類對象,這裏是(MyRunnable mr = new MyRunnable();)
        4. 創建Thread類對象,然後將(這裏是mr)參數傳入。(Thread tr = new Thread(mr);)
        5. 啓動線程
      3. 實例

         //重新編寫繼承Thread類
         public class MyThread extends Thread{
         	//實現線程 分兩種
         	//線程在一個時間點只能運行一個
         	//第一種(步驟):
         	//1、繼承Thread 類
         	//2、重寫run方法 在run方法寫上自己想執行的代碼 因爲線程執行的代碼都在run方法裏面
         	//3、創建線程類對象,這裏是(MyThread mt = new MyThread();)
         	//4、啓動線程
         	//第二種:
         	//1、實現Runnable接口
         	//2、重寫run方法
         	//3、a.創建線程類對象,這裏是(MyRunnable mr = new MyRunnable();)
         	//	 b.創建Thread類對象,然後將(這裏是mr)參數傳入。(Thread tr = new Thread(mr);)
         	//4、啓動線程
         	public void run() {
         		Thread th = Thread.currentThread();//獲取當前線程
         		//Thread.currentThread().getName()獲取當前線程的名字
         		System.out.println("當前線程的名字:"+th.getName());
         		th.setName("MyThread的線程");//設置當前線程的名稱
         		System.out.println(th.getName() + ":" + "設置線程名字後:"+th.getName());
         		System.out.println(th.getName() + ":" + "線程的標識符:"+th.getId());
         		System.out.println(th.getName() + ":" + "線程的優先級:"+th.getPriority());
         		System.out.println(th.getName() + ":" + "最高優先級:"+Thread.MAX_PRIORITY);
         		System.out.println(th.getName() + ":" + "最低優先級:"+Thread.MIN_PRIORITY);
         		System.out.println(th.getName() + ":" + "默認優先級:"+Thread.NORM_PRIORITY);
         		for (int i = 0; i < 10; i++) {
         			try {
         				Thread.sleep(1000);//線程等待,參數傳毫秒數
         				System.out.println(th.getName() + ":" + i);
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         		}
         	}
         }
         //編寫實現Runnable接口類
         public class MyRunnable implements Runnable {
        
         	public void run() {
         		Thread th = Thread.currentThread();// 獲取當前線程
         		// Thread.currentThread().getName()獲取當前線程的名字
         		System.out.println("當前線程的名字:" + th.getName());
         		th.setName("MyRunnable的線程");// 設置當前線程的名稱
         		System.out.println(th.getName() + ":" + "設置線程名字後:" + th.getName());
         		System.out.println(th.getName() + ":" + "線程的標識符:" + th.getId());
         		System.out.println(th.getName() + ":" + "線程的優先級:" + th.getPriority());
         		System.out.println(th.getName() + ":" + "最高優先級:" + Thread.MAX_PRIORITY);
         		System.out.println(th.getName() + ":" + "最低優先級:" + Thread.MIN_PRIORITY);
         		System.out.println(th.getName() + ":" + "默認優先級:" + Thread.NORM_PRIORITY);
         		for (int i = 10; i < 20; i++) {
         			try {
         				Thread.sleep(1000);
         				System.out.println(th.getName() + ":" + i);
         			} catch (Exception e) {
         				e.printStackTrace();
         			}
         
         		}
         	}
         }
         //執行結果
         當前線程的名字:Thread-0
         MyThread的線程:設置線程名字後:MyThread的線程
         MyThread的線程:線程的標識符:11
         MyThread的線程:線程的優先級:5
         MyThread的線程:最高優先級:10
         MyThread的線程:最低優先級:1
         MyThread的線程:默認優先級:5
         當前線程的名字:Thread-1
         MyRunnable的線程:設置線程名字後:MyRunnable的線程
         MyRunnable的線程:線程的標識符:12
         MyRunnable的線程:線程的優先級:5
         MyRunnable的線程:最高優先級:10
         MyRunnable的線程:最低優先級:1
         MyRunnable的線程:默認優先級:5
         MyThread的線程:0
         MyRunnable的線程:10
         MyThread的線程:1
         MyRunnable的線程:11
         MyThread的線程:2
         MyRunnable的線程:12
         MyThread的線程:3
         MyRunnable的線程:13
         MyThread的線程:4
         MyRunnable的線程:14
         MyThread的線程:5
         MyRunnable的線程:15
         MyThread的線程:6
         MyRunnable的線程:16
         MyThread的線程:7
         MyRunnable的線程:17
         MyRunnable的線程:18
         MyThread的線程:8
         MyThread的線程:9
         MyRunnable的線程:19
        
  • 31.1.5 線程同步:synchronized。同步關鍵字,修飾方法或者代碼塊,保證這個代碼塊或者方法每次只有一個線程在執行。

    • 同步方法舉例:

        //取錢類
        public class Account {
        	static double money = 1000000;
        	/**
        	 * 取錢
        	 * @param qMoney 每次取多少錢
        	 */
        	
        	public synchronized void getMoney(double qMoney){
        		money -= qMoney;
        	}
        }
        //取錢的線程
        public class AccountThread extends Thread{
        	private Account account;
        	public AccountThread(Account account) {
        		this.account = account;
        	}
        	
        	public void run() {
        		//取50000次
        		for (int i = 0; i < 50000; i++) {
        			account.getMoney(10);
        		}
        	}
        }
        //測試類
        public class AccountTest {
        	public static void main(String[] args) {
        		Account account = new Account();
        		AccountThread at1 = new AccountThread(account);
        		AccountThread at2 = new AccountThread(account);
        		at1.start();
        		at2.start();
        		try {
        			Thread.sleep(2000);
        		} catch (InterruptedException e) {
        			e.printStackTrace();
        		}
        		System.out.println("剩下的錢:"+Account.money);
        	}
        }
      
    • 同步代碼塊舉例:

      • 舉例1:

          public class ThreadSafeTest extends Thread{
          	public void run() {
          		//其中this代表創建的本線程對象
          		synchronized (this) {
          			for (int i = 0; i < 2; i++) {
          				try {
          					Thread.sleep(1000);
          				} catch (InterruptedException e) {
          					e.printStackTrace();
          				}
          				System.out.println(Thread.currentThread().getName()+":"+i);
          			}
          		}
          	}
          	public static void main(String[] args) {
          		ThreadSafeTest tst = new ThreadSafeTest();
          		Thread t1 = new Thread(tst, "Thread1");
          		Thread t2 = new Thread(tst, "Thread2");
          		t1.start();
          		t2.start();
          	}
          }
          //執行結果
          Thread1:0
          Thread1:1
          Thread2:0
          Thread2:1
        

    結論:執行結果中,可能是Thread1先執行,也可能是Thread2先執行,所以線程之間也是在搶CPU的資源。誰先搶到誰先執行。
    - 舉例2:如果將調用方式修改一下

      		public static void main(String[] args) {
      			ThreadSafeTest tst1 = new ThreadSafeTest();
      			ThreadSafeTest tst2 = new ThreadSafeTest();
      			Thread t1 = new Thread(tst1, "Thread1");
      			Thread t2 = new Thread(tst2, "Thread2");
      			t1.start();
      			t2.start();
      		}
      		//執行結果
      		Thread2:0
      		Thread1:0
      		Thread1:1
      		Thread2:1
      結論:如果創建了兩個ThreadSafeTest則會按照兩個不同的線程進行同步,即例中的this是不同的。所以會同時運行。
    
  • 31.1.6 線程的生命週期

    • 線程的生命週期圖
      [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kx80lOb0-1571042519388)(課上圖片/線程的生命週期.png)]
    • 操作線程的常用方法:
      1. 線程的休眠:sleep(long millis)方法。millis爲毫秒數,當調用這個方法後會使線程中的代碼經過millis毫秒之後繼續執行後續代碼。

      2. 線程等待:wait()方法。即先將線程暫時掛起。然後讓別的線程執行。

      3. 線程喚醒:notify()方法。即將掛起的線程隨機喚醒一個,成爲就緒狀態,繼續執行。

         //例:使用線程實現功能:在程序運行後,要求達到:
         //1.如果會議室爲未滿,則繼續讓人進去。
         //2.如果會議室滿了之後,讓人從會議室走出。3.無限重複1和2的過程。
         /**
          * 人類
          */
         public class Person {
         	static int pCount = 0;//會議室當前人數,會議室能坐20人
         	/**
         	 * 人入場
         	 */
         	public synchronized void in(){
         		if (pCount < 20) {
         			pCount += 1;//每次進來一個人
         			System.out.println("會議室未坐滿,繼續入場:"+pCount);
         		} else {
         			System.out.println("會議室人數已滿,開會了N個小時,準備離場");
         			try {
         				this.notify();//【隨機】通知另一個線程
         				this.wait();//掛起,線程等待
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         		}
         	}
         	/**
         	 * 人離場
         	 */
         	public synchronized void leave(){//synchronized同步的關鍵字
         		if (pCount > 0) {
         			pCount -= 1;
         			System.out.println("會已開完,開始離場:"+pCount);
         		} else {
         			System.out.println("人員已全部離場,準備開始進場");
         			try {
         				this.notify();
         				this.wait();
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         		}
         	}
         }
         /**
          * 入場的線程
          */
         public class InThread extends Thread{
         	private Person person;
         	public InThread(Person person) {
         		this.person = person;
         	}
         
         
         	public void run() {
         		while(true){
         			try {
         				Thread.sleep(1000);
         				person.in();//入場方法
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         		}
         	}
         }
         /**
          * 離場的線程
          */
         public class LeaveThread extends Thread{
         	private Person person;
         	public LeaveThread(Person person) {
         		this.person = person;
         	}
         
         	public void run() {
         		while(true){
         			try {
         				person.leave();//爲了讓離場線程先運行
         				Thread.sleep(1000);
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         		}
         	}
         }
         /**
          * 測試類測試線程
          */
         public class PersonTest {
         	public static void main(String[] args) {
         		Person person = new Person();
         		InThread it = new InThread(person);
         		it.start();
         		LeaveThread lt = new LeaveThread(person);
         		lt.start();
         	}
         }
        
      4. 線程的加入:join()方法:當某個線程使用join()方法加入到另一個線程時,另一個線程會等待該線程執行完畢後再繼續執行。

         //創建線程ThreadA
         public class ThreadA extends Thread{
         	private ThreadB threadB;
         	public ThreadA(ThreadB threadB) {
         		this.threadB = threadB;
         	}
         
         	public void run() {
         		for (int i = 0; i < 5; i++) {
         			try {
         				Thread.sleep(1000);
         				System.out.println(Thread.currentThread().getName()+":"+i);
         				if (i == 2) {//i=2時,加入B線程
         					threadB.start();
         					threadB.join();//在打印一次後加入ThreadB線程
         				}
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         		}
         	}
         }
         //創建線程ThreadB
         public class ThreadB extends Thread{
         	public void run() {
         		Thread.currentThread().setName("ThreadB");
         		for (int i = 'A'; i < 'F'; i++) {
         			try {
         				Thread.sleep(1000);
         			} catch (InterruptedException e) {
         				e.printStackTrace();
         			}
         			System.out.println(Thread.currentThread().getName()+":"+(char)i);
         		}
         	}
         }
         //測試
         public class TestAB {
         	public static void main(String[] args) {
         		Thread threadA = new Thread(new ThreadA(new ThreadB()), "threadA");
         		threadA.start();
         	}
         }
        
      5. 線程的中斷:interrupt()方法

         public class InterruptedThread extends Thread {
         	public void run() {
         		for (int i = 0; i < 5; i++) {
         			try {
         				System.out.println(Thread.currentThread().getName()+":"+i);
         				Thread.sleep(1000);
         			} catch (InterruptedException e) {
         				System.out.println("當前線程被中斷");
         				break;
         			}
         		}
         	}
         	public static void main(String[] args) {
         		InterruptedThread it = new InterruptedThread();
         		Thread thread = new Thread(it, "it");
         		thread.start();
         		thread.interrupt();
         	}
         }
         //執行結果
         it:0
         當前線程被中斷
        
      6. 線程的優先級(注意:優先級越大,最先的機率越大!):

         //線程
         public class PriorityThread implements Runnable{
        
         	@Override
         	public void run() {
         		try {
         			for (int i = 0; i < 10; i++) {
         				System.out.println(Thread.currentThread().getName()+":"+i);
         				Thread.sleep(1000);
         			}
         		} catch (InterruptedException e) {
         			e.printStackTrace();
         		}
         	}
         }
         //測試類
         public class PriorityTest {
         	public static void setPriority(String threadName, int priority, Thread t){
         		t.setPriority(priority);
         		t.setName(threadName);
         		t.start();
         	}
         	public static void main(String[] args) {
         		Thread threadA = new Thread(new PriorityThread());
         		Thread threadB = new Thread(new PriorityThread());
         		Thread threadC = new Thread(new PriorityThread());
         		Thread threadD = new Thread(new PriorityThread());
         		PriorityTest.setPriority("threadA", Thread.MAX_PRIORITY, threadA);
         		PriorityTest.setPriority("threadB", Thread.MIN_PRIORITY, threadB);
         		PriorityTest.setPriority("threadC", Thread.NORM_PRIORITY, threadC);
         		PriorityTest.setPriority("threadD", 3, threadD);
         	}
         }
        

32.2 單例模式

  • 32.2.1 什麼是單例模式:Java中單例(Singleton)模式是一種廣泛使用的設計模式。單例模式的主要作用是保證在Java程序中,某個類只有一個實例存在。一些管理器和控制器常被設計成單例模式。
  • 32.2.2 爲什麼使用單例模式: 單例模式有很多好處,它能夠避免實例對象的重複創建,不僅可以減少每次創建對象的時間開銷,還可以節約內存空間;能夠避免由於操作多個實例導致的邏輯錯誤。如果一個對象有可能貫穿整個應用程序,而且起到了全局統一管理控制的作用,那麼單例模式也許是一個值得考慮的選擇。
  • 32.2.3 單例模式的特點:
    1. 單例類只能有一個實例。
    2. 單例類必須自己創建自己的唯一實例。
    3. 單例類必須給所有其他對象提供這一實例。
  • 32.2.4 單例模式的寫法:單例模式有很多種寫法,大部分寫法都或多或少有一些不足。下面將分別對這幾種寫法進行介紹:
    1. 餓漢模式(常用)
      • 代碼:

          public class Singleton{  
              private static Singleton instance = new Singleton();  
              private Singleton(){
          	
          	}  
              public static Singleton newInstance(){  
                  return instance;  
              }  
          }
        
      • 優缺點:

        • 優點:
          1. 從代碼中我們看到,類的構造函數定義爲private的,保證其他類不能實例化此類,然後提供了一個靜態實例並返回給調用者。
          2. 餓漢模式是最簡單的一種實現方式。
          3. 只在類加載的時候創建一次實例,不會存在多個線程創建多個實例的情況,避免了多線程同步的問題。
        • 缺點:即使這個單例沒有用到也會被創建,而且在類加載之後就被創建,內存就被浪費了。
        • 適用場合:這種實現方式適合單例佔用內存比較小,在初始化時就會被用到的情況。但是,如果單例佔用的內存比較大,或單例只是在某個特定場景下才會用到,使用餓漢模式就不合適了。
    2. 懶漢模式
      • 代碼:
        • 普通模式(線程不安全):
          • 代碼:

              public class Singleton{  
                  private static Singleton instance = null;  
                  private Singleton(){}  
                  public static Singleton newInstance(){  
                      if(null == instance){  
                          instance = new Singleton();  
                      }  
                      return instance;  
                  }  
              }
            
          • 優缺點:

            • 優點:懶漢模式中單例是在需要的時候纔去創建的,如果單例已經創建,再次調用獲取接口將不會重新創建新的對象,而是直接返回之前創建的對象。
            • 缺點:但是這裏的懶漢模式並沒有考慮線程安全問題,在多個線程可能會併發調用它的getInstance()方法,將導致創建多個實例。
            • 使用場合:如果某個單例使用的次數少,並且創建單例消耗的資源較多,那麼就需要實現單例的按需創建,這個時候使用懶漢模式就是一個不錯的選擇。
        • synchronized關鍵字同步模式(線程安全):
          • 代碼:

              public class Singleton{  
                  private static Singleton instance = null;  
                  private Singleton(){}  
                  public static synchronized Singleton newInstance(){  
                      if(null == instance){  
                          instance = new Singleton();  
                      }  
                      return instance;  
                  }  
              }  
            
          • 優缺點:

            • 優點:加鎖的懶漢模式看起來即解決了線程併發問題,又實現了延遲加載。
            • 缺點:synchronized修飾的同步方法比一般方法要慢很多,如果多次調用getInstance(),累積的性能損耗就比較大了。
    3. 雙重校驗鎖模式:
      • 未禁止指令重排序優化模式:
        • 代碼:

            public class Singleton {  
                private static Singleton instance = null;  
                private Singleton(){}  
                public static Singleton getInstance() {  
                    if (instance == null) {  
                        synchronized (Singleton.class) {  
                            if (instance == null) {//2  
                                instance = new Singleton();  
                            }  
                        }  
                    }  
                    return instance;  
                }  
            }
          
        • 優缺點:

          • 優點:
            1. 可以看到上面在同步代碼塊外多了一層instance爲空的判斷。
            2. 由於單例對象只需要創建一次,如果後面再次調用getInstance()只需要直接返回單例對象。因此,大部分情況下,調用getInstance()都不會執行到同步代碼塊,從而提高了程序性能。
            3. 不過還需要考慮一種情況,假如兩個線程A、B,A執行了if (instance == null)語句,它會認爲單例對象沒有創建,此時線程切到B也執行了同樣的語句,B也認爲單例對象沒有創建,然後兩個線程依次執行同步代碼塊,並分別創建了一個單例對象。爲了解決這個問題,還需要在同步代碼塊中增加if (instance == null)語句,也就是上面看到的代碼2。
          • 缺點:這個問題的關鍵就在於由於指令重排優化的存在,導致初始化Singleton和將對象地址賦給instance字段的順序是不確定的。在某個線程創建單例對象時,在構造方法被調用之前,就爲該對象分配了內存空間並將對象的字段設置爲默認值。此時就可以將分配的內存地址賦值給instance字段了,然而該對象可能還沒有初始化。若緊接着另外一個線程來調用getInstance,取到的就是狀態不正確的對象,程序就會出錯。(所謂指令重排優化是指在不改變原語義的情況下,通過調整指令的執行順序讓程序運行的更快。JVM中並沒有規定編譯器優化相關的內容,也就是說JVM可以自由的進行指令重排序的優化。)
      • 禁止指令重排序優化模式:(常用)
        • 代碼:

            public class Singleton {  
                private static volatile Singleton instance = null;  
                private Singleton(){}  
                public static Singleton getInstance() {  
                    if (instance == null) {  
                        synchronized (Singleton.class) {  
                            if (instance == null) {  
                                instance = new Singleton();  
                            }  
                        }  
                    }  
                    return instance;  
                }  
            }
          
        • 優缺點:

          • 優點:保證了instance變量被賦值的時候對象已經是初始化過的,從而避免了上面說到的問題。
    4. 靜態內部類模式:
      • 代碼:

          public class Singleton{  
              private static class SingletonHolder{  
                  public static Singleton instance = new Singleton();  
              }  
              private Singleton(){}  
              public static Singleton newInstance(){  
                  return SingletonHolder.instance;  
              }  
          } 
        
      • 優缺點:

        • 優點:只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會創建單例對象,從而實現懶漢式的延遲加載。也就是說這種方式可以同時保證延遲加載和線程安全。
  • 32.2.5 總結:四種Java中實現單例的方法,其中前兩種都不夠完美,雙重校驗鎖和靜態內部類的方式可以解決大部分問題,平時工作中使用的最多的也是這兩種方式。枚舉方式雖然很完美的解決了各種問題,但是這種寫法多少讓人感覺有些生疏。個人的建議是,在沒有特殊需求的情況下,使用第三種和第四種方式實現單例模式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章