摘要:等待通知機(jī)制利用,實(shí)現(xiàn)的一個(gè)生產(chǎn)者一個(gè)消費(fèi)者和一個(gè)單位的緩存的簡(jiǎn)單模型上面例子中我們生產(chǎn)了一個(gè)數(shù)據(jù)后就需要對(duì)這個(gè)數(shù)據(jù)進(jìn)行消費(fèi)如果生產(chǎn)了但數(shù)據(jù)沒有被獲取則生產(chǎn)線程會(huì)在等待中直到調(diào)用了方法后才會(huì)被繼續(xù)執(zhí)行反之也是一樣的也就是說(shuō)方法是使線程暫停
等待/通知機(jī)制
利用wait,notify實(shí)現(xiàn)的一個(gè)生產(chǎn)者、一個(gè)消費(fèi)者和一個(gè)單位的緩存的簡(jiǎn)單模型:
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."); } }
上面例子中, 我們生產(chǎn)了一個(gè)數(shù)據(jù)后就需要對(duì)這個(gè)數(shù)據(jù)進(jìn)行消費(fèi). 如果生產(chǎn)了但數(shù)據(jù)沒有被獲取, 則生產(chǎn)線程會(huì)在等待中. 直到調(diào)用了 notify() 方法后才會(huì)被繼續(xù)執(zhí)行. 反之也是一樣的.
也就是說(shuō), wait() 方法是使線程暫停; notify() 方法是使線程繼續(xù)運(yùn)行.
但是在使用時(shí)需要注意:
1.執(zhí)行wait, notify時(shí),不獲得鎖會(huì)如何?
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); obj.wait(); obj.notifyAll(); }
執(zhí)行以上代碼, 會(huì)拋出java.lang.IllegalMonitorStateException的異常.
2.執(zhí)行wait, notify時(shí), 不獲得該對(duì)象的鎖會(huì)如何?
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Object lock = new Object(); synchronized (lock) { obj.wait(); obj.notifyAll(); } }
執(zhí)行代碼,同樣會(huì)拋出java.lang.IllegalMonitorStateException的異常
該對(duì)象的鎖 指的就是 obj 對(duì)象的鎖.
3.為什么在執(zhí)行 wait, notify時(shí), 必須獲得該對(duì)象的鎖?
我們需要先知道 synchronized 的作用:
Java中每一個(gè)對(duì)象都可以成為一個(gè)監(jiān)視器(Monitor), 該Monitor由一個(gè)鎖(lock), 一個(gè)等待隊(duì)列(waiting queue), 一個(gè)入口隊(duì)列(entry queue).
對(duì)于一個(gè)對(duì)象的方法, 如果沒有 synchronized 關(guān)鍵字, 該方法可以被任意數(shù)量的線程, 在任意時(shí)刻調(diào)用.
對(duì)于添加了 synchronized 關(guān)鍵字的方法, 任意時(shí)刻只能被唯一的一個(gè)獲得了對(duì)象實(shí)例鎖的線程調(diào)用.
synchronized 用于實(shí)現(xiàn)多線程的同步操作.
當(dāng)一個(gè)線程在執(zhí)行 synchronized 的方法內(nèi)部, 調(diào)用了 wait() 后, 該線程會(huì)釋放該對(duì)象的鎖, 然后該線程會(huì)被添加到該對(duì)象的等待隊(duì)列中(waiting queue), 只要該線程在等待隊(duì)列中, 就會(huì)一直處于閑置狀態(tài), 不會(huì)被調(diào)度執(zhí)行.
要注意 wait() 方法會(huì)強(qiáng)迫線程先進(jìn)行釋放鎖操作, 所以在調(diào)用 wait() 時(shí), 該線程必須已經(jīng)獲得鎖, 否則會(huì)拋出異常(IllegalMonitorStateException). 由于 wait() 在 synchonized 的方法內(nèi)部被執(zhí)行, 鎖一定已經(jīng)獲得, 就不會(huì)拋出異常了.
當(dāng)一個(gè)線程調(diào)用一個(gè)對(duì)象的 notify() 方法時(shí), 調(diào)度器會(huì)從所有處于該對(duì)象等待隊(duì)列 (waiting queue) 的線程中取出任意一個(gè)線程, 將其添加到入口隊(duì)列 (entry queue) 中. 然后在入口隊(duì)列中的多個(gè)線程就會(huì)競(jìng)爭(zhēng)對(duì)象的鎖, 得到鎖的線程就可以繼續(xù)執(zhí)行. 如果等待隊(duì)列中(waiting queue)沒有線程, notify() 方法不會(huì)產(chǎn)生任何作用.
線程狀態(tài)NEW: 線程實(shí)例化時(shí)的默認(rèn)狀態(tài).
RUNNABLE: 一旦線程開始執(zhí)行, 它就會(huì)移動(dòng)到Runnable狀態(tài). 請(qǐng)注意, 等待獲取 CPU 以供執(zhí)行的線程仍處于此狀態(tài).
BLOCKED: 線程一旦被阻塞, 就會(huì)等待監(jiān)視器鎖, 并且移動(dòng)到阻塞狀態(tài). 有兩種方式可以進(jìn)入阻塞狀態(tài).
synchronised 同步代碼塊或同步方法.
調(diào)用 Object.Wait 方法.
WAITING: 調(diào)用下列方法來(lái)將線程變?yōu)榈却隣顟B(tài)
Object.wait without a timeout
Thread.join without a timeout
LockSupport.park
TIMED_WAITING: 調(diào)用下列方法將線程變?yōu)槌瑫r(shí)等待
Thread.sleep
Object.wait with a timeout
Thread.join with a timeout
LockSupport.parkNanos
LockSupport.parkUntil
TERMINATED: 一旦線程終止, 它就會(huì)移動(dòng)到這種狀態(tài).
通過(guò)管道進(jìn)行線程通信: 字節(jié)流用來(lái)讀取管道中的數(shù)據(jù)
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(); } } }
用來(lái)給管道發(fā)送數(shù)據(jù)
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); 用來(lái)將兩個(gè)流之間產(chǎn)生通訊.
對(duì)于字節(jié)流和字符流是一樣的, 只需要使用 PipedWriter 和 PipedReader.join 方法使用
在一個(gè)線程(父線程)中創(chuàng)建另一個(gè)線程(子線程), 有些情況下, 我們需要等待子線程執(zhí)行完成后, 父線程在繼續(xù)執(zhí)行.
比如子線程處理一個(gè)數(shù)據(jù), 父線程要取得這個(gè)數(shù)據(jù)中的值, 可以考慮使用 join 方法來(lái)實(shí)現(xiàn).
不使用 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("我想當(dāng) myThread 執(zhí)行完畢后再執(zhí)行"); System.out.println("但上面代碼中 sleep 中的值應(yīng)該寫多少?"); System.out.println("答案是: 值不能確定 :) "); }使用 join 方法來(lái)解決問題
public static void main(String[] args) throws IOException, InterruptedException { MyThread myThread = new MyThread(); myThread.start(); myThread.join(); System.out.println("我想當(dāng) myThread 對(duì)象執(zhí)行完畢后我再執(zhí)行, 我做到了"); }
join 與 synchronized 的區(qū)別是: join 在內(nèi)部使用 wait 方法進(jìn)行等待, 而 synchronized 關(guān)鍵字使用的是 "對(duì)象監(jiān)視器" 原理做完同步.join(long) 方法的使用
并且如果遇到 interrupt 方法則會(huì)拋出, InterruptedException
方法 join(long) 中的參數(shù)是設(shè)置等待的時(shí)間.
public class MyThread extends Thread { @Override public void run() { try { Thread.sleep(5000); System.out.println("執(zhí)行完成"); } 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秒后執(zhí)行"); }
從打印結(jié)果來(lái)看主線程只等待了兩秒后輸出了 "等待2秒后執(zhí)行", 和 sleep 執(zhí)行結(jié)果是一樣的. 主要原因還是來(lái)自于這2個(gè)方法同步的處理上.
方法 join(long) 與 sleep(long) 的區(qū)別, join(long) 會(huì)釋放鎖, sleep(long) 不會(huì)釋放鎖.ThreadLocal 類的使用
變量值的共享可以使用 public static 變量的形式, 所有的線程都使用同一個(gè) public static 變量. 如果想實(shí)現(xiàn)每一個(gè)線程都有自己的共享變量可以使用 ThreadLocal 類.
類 ThreadLocal 主要解決的就是每個(gè)線程綁定自己的值, 可以比喻成全局存放數(shù)據(jù)的盒子, 盒子中可以存儲(chǔ)每個(gè)線程的私有數(shù)據(jù).
多個(gè)線程之間是隔離的.
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 類可以在子線程中取得父線程繼承下來(lái)的值.
值繼承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 中的值進(jìn)行更改, 那么子線程取到的值還是就值.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73795.html
摘要:閱讀本文約分鐘上一次我們說(shuō)到互斥代碼的實(shí)現(xiàn)過(guò)程,如果有忘記或不清楚的可以去上篇看看。貓說(shuō)多線程之內(nèi)存可見性上篇今天我們了解下重排序。 閱讀本文約3分鐘 上一次我們說(shuō)到synchronized互斥代碼的實(shí)現(xiàn)過(guò)程,如果有忘記或不清楚的可以去上篇看看?!綣ava貓說(shuō)】Java多線程之內(nèi)存可見性(上篇) 今天我們了解下重排序。 其使代碼書寫的順序與實(shí)現(xiàn)執(zhí)行的順序不同,指令重排序是編譯器或處理...
摘要:貓說(shuō)多線程之內(nèi)存可見性下篇?dú)g迎你留言討論屬于你的見解,畢竟每個(gè)人的味蕾都不一樣,這杯咖啡有吸引到你嗎好像又是一個(gè)槽糕的比喻本文已轉(zhuǎn)載個(gè)人技術(shù)公眾號(hào)歡迎留言討論與點(diǎn)贊上一篇推薦貓說(shuō)主數(shù)據(jù)類型和引用下一篇推薦貓說(shuō)多線程之內(nèi)存可見性下篇 閱讀本文約3分鐘 本文大致講述兩種線程實(shí)現(xiàn)的可見性,或許你已經(jīng)提前想到了,那說(shuō)明你的基礎(chǔ)很好,我們要聊聊synchronized實(shí)現(xiàn)可見性與volatil...
摘要:用線程表示維修的過(guò)程維修結(jié)束把廁所置為可用狀態(tài)維修工把廁所修好了,準(zhǔn)備釋放鎖了這個(gè)維修計(jì)劃的內(nèi)容就是當(dāng)維修工進(jìn)入廁所之后,先把門鎖上,然后開始維修,維修結(jié)束之后把的字段設(shè)置為,以表示廁所可用。 線程間通信 如果一個(gè)線程從頭到尾執(zhí)行完也不和別的線程打交道的話,那就不會(huì)有各種安全性問題了。但是協(xié)作越來(lái)越成為社會(huì)發(fā)展的大勢(shì),一個(gè)大任務(wù)拆成若干個(gè)小任務(wù)之后,各個(gè)小任務(wù)之間可能也需要相互協(xié)作最終...
閱讀 3206·2021-09-29 09:34
閱讀 3560·2021-09-10 10:51
閱讀 1960·2021-09-10 10:50
閱讀 6767·2021-08-12 13:31
閱讀 3008·2019-08-30 15:54
閱讀 1585·2019-08-30 15:44
閱讀 1435·2019-08-29 12:26
閱讀 2663·2019-08-26 18:36