等待/通知機制
利用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 關鍵字使用的是 "對象監視器" 原理做完同步.
並且如果遇到 interrupt 方法則會拋出, InterruptedException
join(long) 方法的使用
方法 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 中的值進行更改, 那麼子線程取到的值還是就值.