摘要:等待通知機制利用,實現的一個生產者一個消費者和一個單位的緩存的簡單模型上面例子中我們生產了一個數據后就需要對這個數據進行消費如果生產了但數據沒有被獲取則生產線程會在等待中直到調用了方法后才會被繼續執行反之也是一樣的也就是說方法是使線程暫停
等待/通知機制
利用wait,notify實現的一個生產者、一個消費者和一個單位的緩存的簡單模型:
public class QueueBuffer { int n; boolean valueSet = false; synchronized int get() { if (!valueSet) try { wait(); } catch (InterruptedException e) { System.out.println("InterruptedException caught"); } System.out.println("Got: " + n); valueSet = false; notify(); return n; } synchronized void put(int n) { if (valueSet) try { wait(); } catch (InterruptedException e) { System.out.println("InterruptedException caught"); } this.n = n; valueSet = true; System.out.println("Put: " + n); notify(); } }
public class Producer implements Runnable { private QueueBuffer q; Producer(QueueBuffer q) { this.q = q; new Thread(this, "Producer").start(); } public void run() { int i = 0; while (true) { q.put(i++); } } }
public class Consumer implements Runnable { private QueueBuffer q; Consumer(QueueBuffer q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { while (true) { q.get(); } } }
public class Main { public static void main(String[] args) { QueueBuffer q = new QueueBuffer(); new Producer(q); new Consumer(q); System.out.println("Press Control-C to stop."); } }
上面例子中, 我們生產了一個數據后就需要對這個數據進行消費. 如果生產了但數據沒有被獲取, 則生產線程會在等待中. 直到調用了 notify() 方法后才會被繼續執行. 反之也是一樣的.
也就是說, wait() 方法是使線程暫停; notify() 方法是使線程繼續運行.
但是在使用時需要注意:
1.執行wait, notify時,不獲得鎖會如何?
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); obj.wait(); obj.notifyAll(); }
執行以上代碼, 會拋出java.lang.IllegalMonitorStateException的異常.
2.執行wait, notify時, 不獲得該對象的鎖會如何?
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Object lock = new Object(); synchronized (lock) { obj.wait(); obj.notifyAll(); } }
執行代碼,同樣會拋出java.lang.IllegalMonitorStateException的異常
該對象的鎖 指的就是 obj 對象的鎖.
3.為什么在執行 wait, notify時, 必須獲得該對象的鎖?
我們需要先知道 synchronized 的作用:
Java中每一個對象都可以成為一個監視器(Monitor), 該Monitor由一個鎖(lock), 一個等待隊列(waiting queue), 一個入口隊列(entry queue).
對于一個對象的方法, 如果沒有 synchronized 關鍵字, 該方法可以被任意數量的線程, 在任意時刻調用.
對于添加了 synchronized 關鍵字的方法, 任意時刻只能被唯一的一個獲得了對象實例鎖的線程調用.
synchronized 用于實現多線程的同步操作.
當一個線程在執行 synchronized 的方法內部, 調用了 wait() 后, 該線程會釋放該對象的鎖, 然后該線程會被添加到該對象的等待隊列中(waiting queue), 只要該線程在等待隊列中, 就會一直處于閑置狀態, 不會被調度執行.
要注意 wait() 方法會強迫線程先進行釋放鎖操作, 所以在調用 wait() 時, 該線程必須已經獲得鎖, 否則會拋出異常(IllegalMonitorStateException). 由于 wait() 在 synchonized 的方法內部被執行, 鎖一定已經獲得, 就不會拋出異常了.
當一個線程調用一個對象的 notify() 方法時, 調度器會從所有處于該對象等待隊列 (waiting queue) 的線程中取出任意一個線程, 將其添加到入口隊列 (entry queue) 中. 然后在入口隊列中的多個線程就會競爭對象的鎖, 得到鎖的線程就可以繼續執行. 如果等待隊列中(waiting queue)沒有線程, notify() 方法不會產生任何作用.
線程狀態NEW: 線程實例化時的默認狀態.
RUNNABLE: 一旦線程開始執行, 它就會移動到Runnable狀態. 請注意, 等待獲取 CPU 以供執行的線程仍處于此狀態.
BLOCKED: 線程一旦被阻塞, 就會等待監視器鎖, 并且移動到阻塞狀態. 有兩種方式可以進入阻塞狀態.
synchronised 同步代碼塊或同步方法.
調用 Object.Wait 方法.
WAITING: 調用下列方法來將線程變為等待狀態
Object.wait without a timeout
Thread.join without a timeout
LockSupport.park
TIMED_WAITING: 調用下列方法將線程變為超時等待
Thread.sleep
Object.wait with a timeout
Thread.join with a timeout
LockSupport.parkNanos
LockSupport.parkUntil
TERMINATED: 一旦線程終止, 它就會移動到這種狀態.
通過管道進行線程通信: 字節流用來讀取管道中的數據
public class ReadData extends Thread { private PipedInputStream pipedInputStream; public ReadData(PipedInputStream pipedInputStream) { this.pipedInputStream = pipedInputStream; } @Override public void run() { try { System.out.println("read :"); byte[] byteArray = new byte[20]; int readLen = this.pipedInputStream.read(byteArray); String newData = ""; while(readLen != -1) { newData += new String(byteArray, 0, readLen); readLen = this.pipedInputStream.read(byteArray); } System.out.println(newData); } catch (Exception e) { e.printStackTrace(); } } }
用來給管道發送數據
public class WriteData extends Thread { private PipedOutputStream pipedOutputStream; public WriteData(PipedOutputStream pipedOutputStream) { this.pipedOutputStream = pipedOutputStream; } @Override public void run() { try { System.out.println("write :"); for (int i = 0; i < 300; i++) { String outData = "" + (i + 1); this.pipedOutputStream.write(outData.getBytes()); System.out.print(outData); } System.out.println(); this.pipedOutputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }
public static void main(String[] args) throws IOException { PipedInputStream pipedInputStream = new PipedInputStream(); PipedOutputStream pipedOutputStream = new PipedOutputStream(); pipedOutputStream.connect(pipedInputStream); WriteData writeData = new WriteData(pipedOutputStream); ReadData readData = new ReadData(pipedInputStream); writeData.start(); readData.start(); }
pipedOutputStream.connect(pipedInputStream); 用來將兩個流之間產生通訊.
對于字節流和字符流是一樣的, 只需要使用 PipedWriter 和 PipedReader.join 方法使用
在一個線程(父線程)中創建另一個線程(子線程), 有些情況下, 我們需要等待子線程執行完成后, 父線程在繼續執行.
比如子線程處理一個數據, 父線程要取得這個數據中的值, 可以考慮使用 join 方法來實現.
不使用 join 方法前的問題public class MyThread extends Thread { @Override public void run() { int i = (int) (Math.random() * 10000); System.out.println(i); try { Thread.sleep(i); } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws IOException { MyThread myThread = new MyThread(); myThread.start(); //Thread.sleep(?); System.out.println("我想當 myThread 執行完畢后再執行"); System.out.println("但上面代碼中 sleep 中的值應該寫多少?"); System.out.println("答案是: 值不能確定 :) "); }使用 join 方法來解決問題
public static void main(String[] args) throws IOException, InterruptedException { MyThread myThread = new MyThread(); myThread.start(); myThread.join(); System.out.println("我想當 myThread 對象執行完畢后我再執行, 我做到了"); }
join 與 synchronized 的區別是: join 在內部使用 wait 方法進行等待, 而 synchronized 關鍵字使用的是 "對象監視器" 原理做完同步.join(long) 方法的使用
并且如果遇到 interrupt 方法則會拋出, InterruptedException
方法 join(long) 中的參數是設置等待的時間.
public class MyThread extends Thread { @Override public void run() { try { Thread.sleep(5000); System.out.println("執行完成"); } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws IOException, InterruptedException { MyThread myThread = new MyThread(); myThread.start(); myThread.join(2000); System.out.println("等待2秒后執行"); }
從打印結果來看主線程只等待了兩秒后輸出了 "等待2秒后執行", 和 sleep 執行結果是一樣的. 主要原因還是來自于這2個方法同步的處理上.
方法 join(long) 與 sleep(long) 的區別, join(long) 會釋放鎖, sleep(long) 不會釋放鎖.ThreadLocal 類的使用
變量值的共享可以使用 public static 變量的形式, 所有的線程都使用同一個 public static 變量. 如果想實現每一個線程都有自己的共享變量可以使用 ThreadLocal 類.
類 ThreadLocal 主要解決的就是每個線程綁定自己的值, 可以比喻成全局存放數據的盒子, 盒子中可以存儲每個線程的私有數據.
多個線程之間是隔離的.
public class Tools { public static ThreadLocal threadLocal = new ThreadLocal(); }
public class ThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 100; i++) { Tools.threadLocal.set("ThreadA" + (i + 1)); System.out.println("ThreadA get Value=" + Tools.threadLocal.get()); Thread.sleep(200); } } catch (Exception e) { e.printStackTrace(); } } }
public class ThreadB extends Thread { @Override public void run() { try { for (int i = 0; i < 100; i++) { Tools.threadLocal.set("ThreadB" + (i + 1)); System.out.println("ThreadB get Value=" + Tools.threadLocal.get()); Thread.sleep(200); } } catch (Exception e) { e.printStackTrace(); } } }類 InheritableThreadLocal 的使用
使用 InheritableThreadLocal 類可以在子線程中取得父線程繼承下來的值.
值繼承public class InheritableThreadLocalEx extends InheritableThreadLocal { @Override protected Object initialValue() { return new Date().getTime(); } }
public class Tools { public static InheritableThreadLocalEx inheritableThreadLocalEx = new InheritableThreadLocalEx(); }
public class ThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println("ThreadA get Value=" + Tools.inheritableThreadLocalEx.get()); Thread.sleep(100); } } catch (Exception e) { e.printStackTrace(); } } }
public static void main(String[] args) throws IOException, InterruptedException { for (int i = 0; i < 10; i++) { System.out.println("Main get Value=" + Tools.inheritableThreadLocalEx.get()); Thread.sleep(100); } ThreadA threadA = new ThreadA(); threadA.start(); }值繼承再修改
public class InheritableThreadLocalEx extends InheritableThreadLocal { @Override protected Object initialValue() { return new Date().getTime(); } @Override protected Object childValue(Object parentValue) { return parentValue + " 我在子線程加的~"; } }
注意, 如果子線程在取得值得同, 主線程將 InheritableThreadLocal 中的值進行更改, 那么子線程取到的值還是就值.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73795.html
摘要:閱讀本文約分鐘上一次我們說到互斥代碼的實現過程,如果有忘記或不清楚的可以去上篇看看。貓說多線程之內存可見性上篇今天我們了解下重排序。 閱讀本文約3分鐘 上一次我們說到synchronized互斥代碼的實現過程,如果有忘記或不清楚的可以去上篇看看。【Java貓說】Java多線程之內存可見性(上篇) 今天我們了解下重排序。 其使代碼書寫的順序與實現執行的順序不同,指令重排序是編譯器或處理...
摘要:貓說多線程之內存可見性下篇歡迎你留言討論屬于你的見解,畢竟每個人的味蕾都不一樣,這杯咖啡有吸引到你嗎好像又是一個槽糕的比喻本文已轉載個人技術公眾號歡迎留言討論與點贊上一篇推薦貓說主數據類型和引用下一篇推薦貓說多線程之內存可見性下篇 閱讀本文約3分鐘 本文大致講述兩種線程實現的可見性,或許你已經提前想到了,那說明你的基礎很好,我們要聊聊synchronized實現可見性與volatil...
摘要:用線程表示維修的過程維修結束把廁所置為可用狀態維修工把廁所修好了,準備釋放鎖了這個維修計劃的內容就是當維修工進入廁所之后,先把門鎖上,然后開始維修,維修結束之后把的字段設置為,以表示廁所可用。 線程間通信 如果一個線程從頭到尾執行完也不和別的線程打交道的話,那就不會有各種安全性問題了。但是協作越來越成為社會發展的大勢,一個大任務拆成若干個小任務之后,各個小任務之間可能也需要相互協作最終...
閱讀 3199·2021-09-29 09:34
閱讀 3551·2021-09-10 10:51
閱讀 1948·2021-09-10 10:50
閱讀 6731·2021-08-12 13:31
閱讀 3000·2019-08-30 15:54
閱讀 1560·2019-08-30 15:44
閱讀 1430·2019-08-29 12:26
閱讀 2654·2019-08-26 18:36