一售躁,介紹
本總結(jié)我對于JAVA多線程中線程之間的通信方式的理解,主要以代碼結(jié)合文字的方式來討論線程間的通信,故摘抄了書中的一些示例代碼鞋怀。
二,線程間的通信方式
①同步
這里講的同步是指多個(gè)線程通過synchronized關(guān)鍵字這種方式來實(shí)現(xiàn)線程間的通信持搜。
參考示例:
public class MyObject {
? ? synchronized public void methodA() {
? ? ? ? //do something....
? ? }
? ? synchronized public void methodB() {
? ? ? ? //do some other thing
? ? }
}
public class ThreadA extends Thread {
? ? private MyObject object;
//省略構(gòu)造方法
? ? @Override
? ? public void run() {
? ? ? ? super.run();
? ? ? ? object.methodA();
? ? }
}
public class ThreadB extends Thread {
? ? private MyObject object;
//省略構(gòu)造方法
? ? @Override
? ? public void run() {
? ? ? ? super.run();
? ? ? ? object.methodB();
? ? }
}
public class Run {
? ? public static void main(String[] args) {
? ? ? ? MyObject object = new MyObject();
? ? ? ? //線程A與線程B 持有的是同一個(gè)對象:object
? ? ? ? ThreadA a = new ThreadA(object);
? ? ? ? ThreadB b = new ThreadB(object);
? ? ? ? a.start();
? ? ? ? b.start();
? ? }
}
由于線程A和線程B持有同一個(gè)MyObject類的對象object密似,盡管這兩個(gè)線程需要調(diào)用不同的方法,但是它們是同步執(zhí)行的葫盼,比如:線程B需要等待線程A執(zhí)行完了methodA()方法之后残腌,它才能執(zhí)行methodB()方法。這樣,線程A和線程B就實(shí)現(xiàn)了 通信抛猫。
這種方式蟆盹,本質(zhì)上就是“共享內(nèi)存”式的通信。多個(gè)線程需要訪問同一個(gè)共享變量闺金,誰拿到了鎖(獲得了訪問權(quán)限)逾滥,誰就可以執(zhí)行。
②while輪詢的方式
代碼如下:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class MyList {
5
6? ? private List<String> list = new ArrayList<String>();
7? ? public void add() {
8? ? ? ? list.add("elements");
9? ? }
10? ? public int size() {
11? ? ? ? return list.size();
12? ? }
13 }
14
15 import mylist.MyList;
16
17 public class ThreadA extends Thread {
18
19? ? private MyList list;
20
21? ? public ThreadA(MyList list) {
22? ? ? ? super();
23? ? ? ? this.list = list;
24? ? }
25
26? ? @Override
27? ? public void run() {
28? ? ? ? try {
29? ? ? ? ? ? for (int i = 0; i < 10; i++) {
30? ? ? ? ? ? ? ? list.add();
31? ? ? ? ? ? ? ? System.out.println("添加了" + (i + 1) + "個(gè)元素");
32? ? ? ? ? ? ? ? Thread.sleep(1000);
33? ? ? ? ? ? }
34? ? ? ? } catch (InterruptedException e) {
35? ? ? ? ? ? e.printStackTrace();
36? ? ? ? }
37? ? }
38 }
39
40 import mylist.MyList;
41
42 public class ThreadB extends Thread {
43
44? ? private MyList list;
45
46? ? public ThreadB(MyList list) {
47? ? ? ? super();
48? ? ? ? this.list = list;
49? ? }
50
51? ? @Override
52? ? public void run() {
53? ? ? ? try {
54? ? ? ? ? ? while (true) {
55? ? ? ? ? ? ? ? if (list.size() == 5) {
56? ? ? ? ? ? ? ? ? ? System.out.println("==5, 線程b準(zhǔn)備退出了");
57? ? ? ? ? ? ? ? ? ? throw new InterruptedException();
58? ? ? ? ? ? ? ? }
59? ? ? ? ? ? }
60? ? ? ? } catch (InterruptedException e) {
61? ? ? ? ? ? e.printStackTrace();
62? ? ? ? }
63? ? }
64 }
65
66 import mylist.MyList;
67 import extthread.ThreadA;
68 import extthread.ThreadB;
69
70 public class Test {
71
72? ? public static void main(String[] args) {
73? ? ? ? MyList service = new MyList();
74
75? ? ? ? ThreadA a = new ThreadA(service);
76? ? ? ? a.setName("A");
77? ? ? ? a.start();
78
79? ? ? ? ThreadB b = new ThreadB(service);
80? ? ? ? b.setName("B");
81? ? ? ? b.start();
82? ? }
83 }
在這種方式下败匹,線程A不斷地改變條件寨昙,線程ThreadB不停地通過while語句檢測這個(gè)條件(list.size()==5)是否成立 ,從而實(shí)現(xiàn)了線程間的通信掀亩。但是這種方式會(huì)浪費(fèi)CPU資源舔哪。之所以說它浪費(fèi)資源,是因?yàn)镴VM調(diào)度器將CPU交給線程B執(zhí)行時(shí)槽棍,它沒做啥“有用”的工作捉蚤,只是在不斷地測試 某個(gè)條件是否成立。就類似于現(xiàn)實(shí)生活中炼七,某個(gè)人一直看著手機(jī)屏幕是否有電話來了缆巧,而不是: 在干別的事情,當(dāng)有電話來時(shí)特石,響鈴?fù)ㄖ猅A電話來了盅蝗。關(guān)于線程的輪詢的影響,可參考:JAVA多線程之當(dāng)一個(gè)線程在執(zhí)行死循環(huán)時(shí)會(huì)影響另外一個(gè)線程嗎姆蘸?
這種方式還存在另外一個(gè)問題:
輪詢的條件的可見性問題墩莫,關(guān)于內(nèi)存可見性問題,可參考:JAVA多線程之volatile 與 synchronized 的比較中的第一點(diǎn)“一逞敷,volatile關(guān)鍵字的可見性”
線程都是先把變量讀取到本地線程椏袂兀空間,然后再去再去修改的本地變量推捐。因此裂问,如果線程B每次都在取本地的 條件變量,那么盡管另外一個(gè)線程已經(jīng)改變了輪詢的條件牛柒,它也察覺不到堪簿,這樣也會(huì)造成死循環(huán)。
③wait/notify機(jī)制
代碼如下:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class MyList {
5
6? ? private static List<String> list = new ArrayList<String>();
7
8? ? public static void add() {
9? ? ? ? list.add("anyString");
10? ? }
11
12? ? public static int size() {
13? ? ? ? return list.size();
14? ? }
15 }
16
17
18 public class ThreadA extends Thread {
19
20? ? private Object lock;
21
22? ? public ThreadA(Object lock) {
23? ? ? ? super();
24? ? ? ? this.lock = lock;
25? ? }
26
27? ? @Override
28? ? public void run() {
29? ? ? ? try {
30? ? ? ? ? ? synchronized (lock) {
31? ? ? ? ? ? ? ? if (MyList.size() != 5) {
32? ? ? ? ? ? ? ? ? ? System.out.println("wait begin "
33? ? ? ? ? ? ? ? ? ? ? ? ? ? + System.currentTimeMillis());
34? ? ? ? ? ? ? ? ? ? lock.wait();
35? ? ? ? ? ? ? ? ? ? System.out.println("wait end? "
36? ? ? ? ? ? ? ? ? ? ? ? ? ? + System.currentTimeMillis());
37? ? ? ? ? ? ? ? }
38? ? ? ? ? ? }
39? ? ? ? } catch (InterruptedException e) {
40? ? ? ? ? ? e.printStackTrace();
41? ? ? ? }
42? ? }
43 }
44
45
46 public class ThreadB extends Thread {
47? ? private Object lock;
48
49? ? public ThreadB(Object lock) {
50? ? ? ? super();
51? ? ? ? this.lock = lock;
52? ? }
53
54? ? @Override
55? ? public void run() {
56? ? ? ? try {
57? ? ? ? ? ? synchronized (lock) {
58? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
59? ? ? ? ? ? ? ? ? ? MyList.add();
60? ? ? ? ? ? ? ? ? ? if (MyList.size() == 5) {
61? ? ? ? ? ? ? ? ? ? ? ? lock.notify();
62? ? ? ? ? ? ? ? ? ? ? ? System.out.println("已經(jīng)發(fā)出了通知");
63? ? ? ? ? ? ? ? ? ? }
64? ? ? ? ? ? ? ? ? ? System.out.println("添加了" + (i + 1) + "個(gè)元素!");
65? ? ? ? ? ? ? ? ? ? Thread.sleep(1000);
66? ? ? ? ? ? ? ? }
67? ? ? ? ? ? }
68? ? ? ? } catch (InterruptedException e) {
69? ? ? ? ? ? e.printStackTrace();
70? ? ? ? }
71? ? }
72 }
73
74 public class Run {
75
76? ? public static void main(String[] args) {
77
78? ? ? ? try {
79? ? ? ? ? ? Object lock = new Object();
80
81? ? ? ? ? ? ThreadA a = new ThreadA(lock);
82? ? ? ? ? ? a.start();
83
84? ? ? ? ? ? Thread.sleep(50);
85
86? ? ? ? ? ? ThreadB b = new ThreadB(lock);
87? ? ? ? ? ? b.start();
88? ? ? ? } catch (InterruptedException e) {
89? ? ? ? ? ? e.printStackTrace();
90? ? ? ? }
91? ? }
92 }
線程A要等待某個(gè)條件滿足時(shí)(list.size()==5)皮壁,才執(zhí)行操作椭更。線程B則向list中添加元素,改變list 的size蛾魄。
A,B之間如何通信的呢虑瀑?也就是說湿滓,線程A如何知道 list.size() 已經(jīng)為5了呢?
這里用到了Object類的 wait() 和 notify() 方法舌狗。
當(dāng)條件未滿足時(shí)(list.size() !=5)叽奥,線程A調(diào)用wait() 放棄CPU,并進(jìn)入阻塞狀態(tài)痛侍。---不像②while輪詢那樣占用CPU
當(dāng)條件滿足時(shí)朝氓,線程B調(diào)用 notify()通知 線程A,所謂通知線程A主届,就是喚醒線程A膀篮,并讓它進(jìn)入可運(yùn)行狀態(tài)。
這種方式的一個(gè)好處就是CPU的利用率提高了岂膳。
但是也有一些缺點(diǎn):比如,線程B先執(zhí)行磅网,一下子添加了5個(gè)元素并調(diào)用了notify()發(fā)送了通知谈截,而此時(shí)線程A還執(zhí)行;當(dāng)線程A執(zhí)行并調(diào)用wait()時(shí)涧偷,那它永遠(yuǎn)就不可能被喚醒了簸喂。因?yàn)椋€程B已經(jīng)發(fā)了通知了燎潮,以后不再發(fā)通知了喻鳄。這說明:通知過早,會(huì)打亂程序的執(zhí)行邏輯确封。
④管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream進(jìn)行通信
具體就不介紹了除呵。分布式系統(tǒng)中說的兩種通信機(jī)制:共享內(nèi)存機(jī)制和消息通信機(jī)制。感覺前面的①中的synchronized關(guān)鍵字和②中的while輪詢 “屬于” 共享內(nèi)存機(jī)制爪喘,由于是輪詢的條件使用了volatile關(guān)鍵字修飾時(shí)颜曾,這就表示它們通過判斷這個(gè)“共享的條件變量“是否改變了,來實(shí)現(xiàn)進(jìn)程間的交流秉剑。
而管道通信泛豪,更像消息傳遞機(jī)制,也就是說:通過管道侦鹏,將一個(gè)線程中的消息發(fā)送給另一個(gè)诡曙。