請參看前一篇文章:Java 併發學習筆記(一)——原子性、可見性、有序性問題java
什麼是等待通知—機制?當線程不知足某個條件,則進入等待狀態;若是線程知足要求的某個條件後,則通知等待的線程從新執行。編程
等待通知機制的流程通常是這樣的:線程首先獲取互斥鎖,當不知足某個條件的時候,釋放互斥鎖,並進入這個條件的等待隊列;一直等到知足了這個條件以後,通知等待的線程,而且須要從新獲取互斥鎖。segmentfault
等待-通知機制可使用 Java 的 synchronized 關鍵字,配合 wait()、notify()、notifyAll() 這個三個方法來實現。安全
前面說到的解決死鎖問題的那個例子,一次性申請全部的資源,使用的是循環等待,這在併發量很大的時候比較消耗 CPU 資源。併發
如今使用等待-通知機制進行優化:app
final class Monitor { private List<Object> res = new ArrayList<>(2); /** * 一次性申請資源 */ public synchronized void apply(Object resource1, Object resource2) { while (res.contains(resource1) || res.contains(resource2)){ try { //條件不知足則進入等待隊列 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } res.add(resource1); res.add(resource2); } /** * 歸還資源 */ public synchronized void free(Object resource1, Object resource2){ res.remove(resource1); res.remove(resource2); //釋放資源以後,通知等待的線程開始執行 this.notifyAll(); } }
1) 每一個互斥鎖都有相應的等待隊列,例如上面的例子,就存在兩個等待隊列,一是 synchronized 入口等待隊列,二是 while 循環這個條件的等待隊列。ide
2) 調用 wait() 方法,會使當前線程釋放持有的鎖,並進入這個條件的等待隊列。知足條件以後,隊列中的線程被喚醒,不是立刻執行,而是須要從新獲取互斥鎖。例如上圖中,if 條件的隊列中的線程被喚醒後,須要從新進入 synchronized 處獲取互斥鎖。學習
相同點:兩個方法都會讓渡 CPU 的使用權,等待再次被調度。優化
不一樣點:this
指的是對共享變量和對共享變量的操做的管理,使其支持併發,對應到 Java,指的是管理類的成員變量和方法,讓這個類是線程安全的。
管程主要的模型有 Hasen、Hoare、MESA ,其中 MESA 最經常使用。管程的 MESA 模型主要解決的是線程的互斥和同步問題,和上面說到的等待-通知機制十分相似。示意圖以下:
首先看看管程是如何實現互斥的?在管程的入口有一個等待隊列,一次只容許一個線程進入管程。每一個條件對應一個等待隊列,當線程不知足條件的時候,進入對應的等待隊列;當條件知足的時候,隊列中的線程被喚醒,從新進入到入口處的等待隊列獲取互斥鎖,這就實現了線程的同步問題。
接下來使用代碼實現了一個簡單的阻塞隊列,這就是一個很典型的管程模型,解決了線程互斥和同步問題。
public class BlockingQueue<T> { private int capacity; private int size; private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); /** * 入隊列 */ public void enqueue(T data){ lock.lock(); try { //若是隊列滿了,須要等待,直到隊列不滿 while (size >= capacity){ notFull.await(); } //入隊代碼,省略 //入隊以後,通知隊列已經不爲空了 notEmpty.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 出隊列 */ public T dequeue(){ lock.lock(); try { //若是隊列爲空,須要等待,直到隊列不爲空 while (size <= 0){ notEmpty.await(); } //出隊代碼,省略 //出隊列以後,通知隊列已經不滿了 notFull.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } //實際應該返回出隊數據 return null; } }
Java 中的線程共分爲了 6 種狀態,分別是:
NEW 到 RUNNABLE 狀態的轉換:在 Java 中新建立的線程,會當即進入 NEW 狀態,而後啓動線程進入 RUNNABLE 狀態。Java 中新建線程通常有三種方式:
繼承 Thread 類
public class MyThread extends Thread { @Override public void run() { System.out.println("I am roseduan"); } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
實現 Runnable 接口,並將其實現類傳給 Thread 做爲參數
public class MyThread { public static void main(String[] args) { Thread thread = new Thread(new Print()); thread.start(); } } class Print implements Runnable{ @Override public void run() { System.out.println("I am roseduan"); } }
實現 Collable 接口,將其實現類傳給線程池執行,而且能夠獲取返回結果
public class ThreadTest { public static void main(String[] args) throws InterruptedException { //線程池 BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 1, TimeUnit.HOURS, queue); //執行 Future<?> submit = threadPool.submit(new Demo()); } } class Demo implements Callable<String> { @Override public String call() { System.out.println("I am roseduan"); return "I am roseduan"; } }
局部變量存在於方法中,每一個方法都有對應的調用棧幀,因爲每一個線程都有本身獨立的方法調用棧,所以局部變量並無被共享。因此即使多個線程同時調用同一個方法,方法內部的局部變量也是線程安全的,不須要單獨加鎖。
經極客時間《Java 併發編程實戰》專欄內容學習整理