1.wait/notify都是Object的方法,java爲所有的對象都提供了這兩個方法。
2.wait和notify必須配合synchronized關鍵字使用
3.wait方法釋放鎖,notify方法不釋放鎖
最開始不用wait/notify的實現,線程t1加元素,t2檢測到t1的元素長度爲5的時候停止t2線程。
package com.wq.線程間通信;
import java.util.ArrayList;
import java.util.List;
/**
* @author wangqiang
* @date 2018/12/17 18:14
*/
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add(){
list.add("abcde");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd1 list1 = new ListAdd1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
list1.add();
System.out.println("當前線程:"+Thread.currentThread().getName()+"" +
"添加了一個新元素");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true){
if (list1.size()==5){
System.out.println("當前線程收到通知:"+Thread.currentThread().getName()+"" +
"list1.size()==5停止");
throw new RuntimeException();
}
}
}
},"t2");
t1.start();
t2.start();
}
}
wait和notify必須配合synchronized關鍵字使用,
使用同一個對象鎖。 notify()發出通知,但不釋放鎖。wait是釋放鎖的。所以t2執行完之後,t1能獲得鎖
例子
package com.wq.線程間通信;
import java.util.ArrayList;
import java.util.List;
/**
* @author wangqiang
* @date 2018/12/17 18:31
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add() {
list.add("abcde");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
list2.add();
System.out.println("當前線程:" + Thread.currentThread().getName() + "" +
"添加了一個新元素");
Thread.sleep(500);
if (list2.size() == 5) {
System.out.println("已經發出通知..");
lock.notify(); //不釋放鎖 等線程執行完之後才釋放
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (list2.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("當前線程收到通知:" + Thread.currentThread().getName() + "" +
"list1.size()==5停止");
throw new RuntimeException();
}
}
}
}, "t2");
t2.start();
t1.start();
}
t2線程先啓動。
}
運行結果:
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
已經發出通知..
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
Exception in thread "t2" 當前線程收到通知:t2list1.size()==5停止
java.lang.RuntimeException
at com.wq.線程間通信.ListAdd2$2.run(ListAdd2.java:69)
at java.lang.Thread.run(Thread.java:748)
但是上述的不是實時,要等到t1執行完之後,t2才能執行
使用CountDownLatch使這個變得實時
package com.wq.線程間通信;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @author wangqiang
* @date 2018/12/17 18:31
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add() {
list.add("abcde");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
CountDownLatch countDownLatch = new CountDownLatch(1);
//表示一個發送幾個通知。。countDown()多少次
// final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
// synchronized (lock) {
for (int i = 0; i < 10; i++) {
list2.add();
System.out.println("當前線程:" + Thread.currentThread().getName() + "" +
"添加了一個新元素");
Thread.sleep(500);
if (list2.size() == 5) {
System.out.println("已經發出通知..");
countDownLatch.countDown();
// lock.notify(); //不釋放鎖 等線程執行完之後才釋放
}
}
// }
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// synchronized (lock) {
if (list2.size() != 5) {
try {
countDownLatch.await();//等待
// System.out.println("呵呵,竟然說我沒執行");
// lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("當前線程收到通知:" + Thread.currentThread().getName() + "" +
"list1.size()==5停止");
throw new RuntimeException();
}
// }
}
}, "t2");
t2.start();
t1.start();
}
}
輸出結果:
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
已經發出通知..
當前線程:t1添加了一個新元素
當前線程收到通知:t2list1.size()==5停止
Exception in thread "t2" java.lang.RuntimeException
at com.wq.線程間通信.ListAdd2$2.run(ListAdd2.java:77)
at java.lang.Thread.run(Thread.java:748)
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
當前線程:t1添加了一個新元素
引申:
CountDownLatch是什麼
CountDownLatch是在java1.5被引入的,跟它一起被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作後再執行。例如,應用程序的主線程希望在負責啓動框架服務的線程已經啓動所有的框架服務之後再執行。
CountDownLatch是通過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。
CountDownLatch的僞代碼如下所示:
//Main thread start
//Create CountDownLatch for N threads
//Create and start N threads
//Main thread wait on latch
//N threads completes there tasks are returns
//Main thread resume execution
CountDownLatch如何工作
CountDownLatch.java類中定義的構造函數:
//Constructs a CountDownLatch initialized with the given count.
public void CountDownLatch(int count) {...}
構造器中的計數值(count)實際上就是閉鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownLatch沒有提供任何機制去重新設置這個計數值。
與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啓動其他線程後立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。
其他N 個線程必須引用閉鎖對象,因爲他們需要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是通過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調 用了這個方法,count的值等於0,然後主線程就能通過await()方法,恢復執行自己的任務。
在實時系統中的使用場景
讓我們嘗試羅列出在java實時系統中CountDownLatch都有哪些使用場景。我所羅列的都是我所能想到的。如果你有別的可能的使用方法,請在留言裏列出來,這樣會幫助到大家。
- 實現最大的並行性:有時我們想同時啓動多個線程,實現最大程度的並行性。例如,我們想測試一個單例類。如果我們創建一個初始計數爲1的CountDownLatch,並讓所有線程都在這個鎖上等待,那麼我們可以很輕鬆地完成測試。我們只需調用 一次countDown()方法就可以讓所有的等待線程同時恢復執行。
- 開始執行前等待n個線程完成各自任務:例如應用程序啓動類要確保在處理用戶請求前,所有N個外部系統已經啓動和運行了。
- 死鎖檢測:一個非常方便的使用場景是,你可以使用n個線程訪問共享資源,在每次測試階段的線程數目是不同的,並嘗試產生死鎖。