使用輪詢方式實現通信:
public class MyList {
private static int i = 0;
public int getI() {
return i;
}
public void setI(int i) {
MyList.i = i;
}
private volatile List<String> list = new ArrayList<String>();
public void add() {
list.add("elements");
}
public int size() {
return list.size();
}
}
public class ThreadA implements Runnable{
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
list.add();
System.out.println(Thread.currentThread().getName()+"添加了" + (i + 1) + "個元素");
Thread.sleep(2000);
if(list.getI()!= 0){
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadB implements Runnable{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"判斷當前容量。");
if (list.size() == 5) {
System.out.println("線程b準備退出了");
list.setI(list.size());
break;
}
}
}
}
public class Main {
public static void main(String[] args) {
MyList service = new MyList();
Thread thread1 = new Thread(new ThreadA(service),"A");
Thread thread2 = new Thread(new ThreadB(service),"B");
thread1.start();
thread2.start();
}
}
在這種方式下,線程A不斷地改變條件,線程ThreadB不停地通過while語句檢測這個條件(list.size()==5)是否成立 ,從而實現了線程間的通信。但是這種方式會浪費CPU資源。之所以說它浪費資源,是因爲JVM調度器將CPU交給線程B執行時,它沒做啥“有用”的工作,只是在不斷地測試
某個條件是否成立。就類似於現實生活中,某個人一直看着手機屏幕是否有電話來了,而不是: 在幹別的事情,當有電話來時,響鈴通知TA電話來了。注意MyList中變量list是否爲volatile的區別。詳見:volatile關鍵字的初步理解
使用wait()/notify()方式實現通信:
public class MyList {
private static volatile List<String> list = new ArrayList<String>();
public static void add() {
list.add("anyString");
}
public static int size() {
return list.size();
}
}
public class ThreadA extends Thread{
private Object lock;
public ThreadA(Object o) {
this.lock = o;
}
@Override
public void run() {
try {
synchronized (lock) {
if (MyList.size() < 5) {
System.out.println(Thread.currentThread().getName()+"wait begin "
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
lock.wait();
System.out.println(Thread.currentThread().getName()+"wait end "
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadB extends Thread{
private Object lock;
public ThreadB(Object o) {
this.lock = o;
}
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
System.out.println(Thread.currentThread().getName()+"添加了" + (i + 1) + "個元素!");
if (MyList.size() == 5) {
lock.notify();
System.out.println(Thread.currentThread().getName()+"已經發出了通知");
}
if(MyList.size() > 5){
System.out.println("你要執行嗎?");
Thread.yield();
}
Thread.sleep(500);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
try {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
ThreadB b = new ThreadB(lock);
a.start();
Thread.sleep(50);
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程A要等待某個條件滿足時(list.size()==5),才執行操作。線程B則向list中添加元素,改變list 的size。
A,B之間如何通信的呢?也就是說,線程A如何知道 list.size() 已經爲5了呢?
這裏用到了Object類的 wait() 和 notify() 方法。
當條件未滿足時(list.size() !=5),線程A調用wait() 放棄CPU,並進入阻塞狀態。---不像while輪詢那樣佔用CPU
當條件滿足時,線程B調用 notify()通知 線程A,所謂通知線程A,就是喚醒線程A,並讓它進入可運行狀態。
這種方式的一個好處就是CPU的利用率提高了。
但是也有一些缺點:比如,線程B先執行,一下子添加了5個元素並調用了notify()發送了通知,而此時線程A還執行;當線程A執行並調用wait()時,那它永遠就不可能被喚醒了。因爲,線程B已經發了通知了,以後不再發通知了。這說明:通知過早,會打亂程序的執行邏輯。
我以爲notify之後處於wait狀態的代碼會立即執行,至少在yield之後會立即執行,但是昨天問了一位同事之後,說是要等同步代碼塊執行完之後纔會釋放鎖讓處於wait的線程執行。