Java多线程编程-(4)-线程间通信机制的介绍与使用
上一篇:
Java多線程編程-(1)-線程安全和鎖Synchronized概念
Java多線程編程-(2)-可重入鎖以及Synchronized的其他基本特性
Java多線程編程-(3)-線程本地ThreadLocal的介紹與使用
線程間通信簡(jiǎn)介
我們知道線程是操作系統(tǒng)中獨(dú)立的個(gè)體,但是這個(gè)單獨(dú)的個(gè)體之間沒有一種特殊的處理方式使之成為一個(gè)整體,線程之間沒有任何交流和溝通的話,他就是一個(gè)個(gè)單獨(dú)的個(gè)體,不足以形成一個(gè)強(qiáng)大的交互性較強(qiáng)的整體。
為了提高CPU的利用率和各線程之間相互協(xié)作,Java的一種實(shí)現(xiàn)線程間通信的機(jī)制是:wait/notify線程間通信,下邊就一起學(xué)習(xí)一下這種線程間的通信機(jī)制。
不使用等待/通知機(jī)制實(shí)現(xiàn)線程間通信
假如,我們不使用下邊需要介紹的機(jī)制,那我們?nèi)绾螌?shí)現(xiàn)兩個(gè)線程之間的通信哪,下邊看一段代碼,實(shí)現(xiàn)的是兩個(gè)線程向一個(gè)List里填充數(shù)據(jù):
MyList代碼:
public class MyList {
? ? private List list = new ArrayList();
? ? public void add() {
? ? ? ? list.add("我是元素");
? ? }
? ? public int size() {
? ? ? ? return list.size();
? ? }
}
線程A:
public class ThreadA extends Thread {
? ? private MyList list;
? ? public ThreadA(MyList list) {
? ? ? ? super();
? ? ? ? this.list = list;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? try {
? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? list.add();
? ? ? ? ? ? ? ? System.out.println("添加了" + (i + 1) + "個(gè)元素");
? ? ? ? ? ? ? ? Thread.sleep(1000);
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
線程B:
public class ThreadB extends Thread {
? ? private MyList list;
? ? public ThreadB(MyList list) {
? ? ? ? super();
? ? ? ? this.list = list;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? try {
? ? ? ? ? ? while (true) {
? ? ? ? ? ? ? ? if (list.size() == 5) {
? ? ? ? ? ? ? ? ? ? System.out.println("==5了,線程b要退出了!");
? ? ? ? ? ? ? ? ? ? throw new InterruptedException();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
測(cè)試類Test:
public class Test {
? ? public static void main(String[] args) {
? ? ? ? MyList myList = new MyList();
? ? ? ? ThreadA a = new ThreadA(myList);
? ? ? ? a.setName("A");
? ? ? ? a.start();
? ? ? ? ThreadB b = new ThreadB(myList);
? ? ? ? b.setName("B");
? ? ? ? b.start();
? ? }
}
執(zhí)行結(jié)果:
添加了1個(gè)元素
添加了2個(gè)元素
添加了3個(gè)元素
添加了4個(gè)元素
添加了5個(gè)元素
==5了,線程b要退出了!
java.lang.InterruptedException
? ? at text.ThreadB.run(ThreadB.java:20)
添加了6個(gè)元素
添加了7個(gè)元素
添加了8個(gè)元素
添加了9個(gè)元素
添加了10個(gè)元素
可以看出,當(dāng)List集合中的數(shù)據(jù)為5個(gè)的時(shí)候線程B退出,雖然兩個(gè)線程之間實(shí)現(xiàn)了通信,但是代碼中我們的線程B是一直執(zhí)行著while(true) 循環(huán)的,直到長(zhǎng)度為5才終止執(zhí)行,顯然這種方式是很消耗資源的。所以,就需要一種機(jī)制能避免上述的操作又能實(shí)現(xiàn)多個(gè)線程之間的通信,這就是接下來需要學(xué)習(xí)的“wait/notify線程間通信”。
什么是等待/通知機(jī)制
道理很簡(jiǎn)單,就像我們?nèi)ャy行辦業(yè)務(wù),進(jìn)門之后取票號(hào),等到達(dá)的時(shí)候會(huì)廣播通知我們辦業(yè)務(wù)一樣,這就是很實(shí)際的一個(gè)場(chǎng)景,我們?nèi)×似碧?hào)就需要等待,等業(yè)務(wù)員輪到票號(hào)的時(shí)候就會(huì)廣播通知。
Java中等待/通知機(jī)制的實(shí)現(xiàn)
Java中對(duì)應(yīng)等待/通知的方法是wait()/notify(),這兩個(gè)方法都是超類Object中的方法,如下圖所示:
之所以會(huì)是超類Object中的方法,我們可以簡(jiǎn)單的理解:上幾篇文章中我們知道任何對(duì)象都可以作為鎖,而wait()/notify()是由鎖調(diào)用的,想到這里自然可以體會(huì)到這里設(shè)計(jì)的巧妙之處。
一、wait方法
(1)方法wait()的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,該方法會(huì)將該線程放入”預(yù)執(zhí)行隊(duì)列“中,并且在wait()所在的代碼處停止執(zhí)行,直到接到通知或被中斷為止。
(2)在調(diào)用wait()之前,線程必須獲得該對(duì)象級(jí)別鎖,這是一個(gè)很重要的地方,很多時(shí)候我們可能會(huì)忘記這一點(diǎn),即只能在同步方法或同步塊中調(diào)用wait()方法。
(3)還需要注意的是wait()是釋放鎖的,即在執(zhí)行到wait()方法之后,當(dāng)前線程會(huì)釋放鎖,當(dāng)從wait()方法返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖。
二、notify方法
(1)和wait()方法一樣,notify()方法也要在同步塊或同步方法中調(diào)用,即在調(diào)用前,線程也必須獲得該對(duì)象的對(duì)象級(jí)別鎖。
(2)該方法是用來通知那些可能等待該對(duì)象的對(duì)象鎖的其他線程,如果有多個(gè)線程等待,則由線程規(guī)劃器隨機(jī)挑選出其中一個(gè)呈wait狀態(tài)的線程,對(duì)其發(fā)出通知notify,并使它等待獲取該對(duì)象的對(duì)象鎖。
(3)這里需要注意的是,執(zhí)行notify方法之后,當(dāng)前線程不會(huì)立即釋放其擁有的該對(duì)象鎖,而是執(zhí)行完之后才會(huì)釋放該對(duì)象鎖,被通知的線程也不會(huì)立即獲得對(duì)象鎖,而是等待notify方法執(zhí)行完之后,釋放了該對(duì)象鎖,才可以獲得該對(duì)象鎖。
(3)notifyAll()通知所有等待同一共享資源的全部線程從等待狀態(tài)退出,進(jìn)入可運(yùn)行狀態(tài),重新競(jìng)爭(zhēng)獲得對(duì)象鎖。
三、wait()/notify()方法總結(jié)
(1)wait()/notify()要集合synchronized關(guān)鍵字一起使用,因?yàn)樗麄兌夹枰紫全@取該對(duì)象的對(duì)象鎖;
(2)wait方法是釋放鎖,notify方法是不釋放鎖的;
(3)線程的四種狀態(tài)如下圖:
wait/notify線程間通信示例代碼
根據(jù)上述不使用wait/notify的代碼改造如下:
MyList代碼:
public class MyList {
? ? private static List list = new ArrayList();
? ? public static void add() {
? ? ? ? list.add("我是元素");
? ? }
? ? public static int size() {
? ? ? ? return list.size();
? ? }
}
線程A:
public class ThreadA extends Thread {
? ? private Object lock;
? ? public ThreadA(Object lock) {
? ? ? ? super();
? ? ? ? this.lock = lock;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? try {
? ? ? ? ? ? synchronized (lock) {
? ? ? ? ? ? ? ? if (MyList.size() != 5) {
? ? ? ? ? ? ? ? ? ? System.out.println("wait begin " + System.currentTimeMillis());
? ? ? ? ? ? ? ? ? ? lock.wait();
? ? ? ? ? ? ? ? ? ? System.out.println("wait end ?" + System.currentTimeMillis());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
線程B:
public class ThreadB extends Thread {
? ? private Object lock;
? ? public ThreadB(Object lock) {
? ? ? ? super();
? ? ? ? this.lock = lock;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? try {
? ? ? ? ? ? synchronized (lock) {
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? MyList.add();
? ? ? ? ? ? ? ? ? ? if (MyList.size() == 5) {
? ? ? ? ? ? ? ? ? ? ? ? lock.notify();
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("已發(fā)出通知!");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? System.out.println("添加了" + (i + 1) + "個(gè)元素!");
? ? ? ? ? ? ? ? ? ? Thread.sleep(1000);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
測(cè)試代碼:
public class Run {
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? Object lock = new Object();
? ? ? ? ? ? ThreadA a = new ThreadA(lock);
? ? ? ? ? ? a.start();
? ? ? ? ? ? Thread.sleep(50);
? ? ? ? ? ? ThreadB b = new ThreadB(lock);
? ? ? ? ? ? b.start();
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
運(yùn)行結(jié)果:
wait begin 1507634541467
添加了1個(gè)元素!
添加了2個(gè)元素!
添加了3個(gè)元素!
添加了4個(gè)元素!
已發(fā)出通知!
添加了5個(gè)元素!
添加了6個(gè)元素!
添加了7個(gè)元素!
添加了8個(gè)元素!
添加了9個(gè)元素!
添加了10個(gè)元素!
wait end ?1507634551563
上述實(shí)例已經(jīng)實(shí)現(xiàn)了簡(jiǎn)單的等待通知機(jī)制,并且我們也可以看到,雖然線程B在第五個(gè)元素的時(shí)候發(fā)出通知,而線程A實(shí)現(xiàn)線程B執(zhí)行完之后才獲得對(duì)象鎖,這也可以說明,wait方法是釋放鎖的而notify方法是不釋放鎖的。
另一個(gè)案例:使用wait/notify模擬BlockingQueue阻塞隊(duì)列
BlockingQueue是阻塞隊(duì)列,我們需要實(shí)現(xiàn)的是阻塞的放入和得到數(shù)據(jù),設(shè)計(jì)思路如下:
(1)初始化隊(duì)列最大長(zhǎng)度為5;?
(2)需要新加入的時(shí)候,判斷是否長(zhǎng)度為5,如果是5則等待插入;?
(3)需要消費(fèi)元素的時(shí)候,判斷是否為0,如果是0則等待消費(fèi);
實(shí)現(xiàn)代碼如下:
public class MyQueue {
? ? //1、需要一個(gè)承裝元素的集合
? ? private final LinkedList<Object> list = new LinkedList<>();
? ? //2、需要一個(gè)計(jì)數(shù)器
? ? private final AtomicInteger count = new AtomicInteger(0);
? ? //3、需要指定上限和下限
? ? private final int maxSize = 5;
? ? private final int minSize = 0;
? ? //5、初始化鎖對(duì)象
? ? private final Object lock = new Object();
? ? /**
? ? ?* put方法
? ? ?*/
? ? public void put(Object obj) {
? ? ? ? synchronized (lock) {
? ? ? ? ? ? //達(dá)到最大無法添加,進(jìn)入等到
? ? ? ? ? ? while (count.get() == maxSize) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? lock.wait();
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? list.add(obj); //加入元素
? ? ? ? ? ? count.getAndIncrement(); //計(jì)數(shù)器增加
? ? ? ? ? ? System.out.println(" 元素 " + obj + " 被添加 ");
? ? ? ? ? ? lock.notify(); //通知另外一個(gè)阻塞的線程方法
? ? ? ? }
? ? }
? ? /**
? ? ?* get方法
? ? ?*/
? ? public Object get() {
? ? ? ? Object temp;
? ? ? ? synchronized (lock) {
? ? ? ? ? ? //達(dá)到最小,沒有元素?zé)o法消費(fèi),進(jìn)入等到
? ? ? ? ? ? while (count.get() == minSize) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? lock.wait();
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? count.getAndDecrement();
? ? ? ? ? ? temp = list.removeFirst();
? ? ? ? ? ? System.out.println(" 元素 " + temp + " 被消費(fèi) ");
? ? ? ? ? ? lock.notify();
? ? ? ? }
? ? ? ? return temp;
? ? }
? ? private int size() {
? ? ? ? return count.get();
? ? }
? ? public static void main(String[] args) throws Exception {
? ? ? ? final MyQueue myQueue = new MyQueue();
? ? ? ? initMyQueue(myQueue);
? ? ? ? Thread t1 = new Thread(() -> {
? ? ? ? ? ? myQueue.put("h");
? ? ? ? ? ? myQueue.put("i");
? ? ? ? }, "t1");
? ? ? ? Thread t2 = new Thread(() -> {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? Thread.sleep(2000);
? ? ? ? ? ? ? ? myQueue.get();
? ? ? ? ? ? ? ? Thread.sleep(2000);
? ? ? ? ? ? ? ? myQueue.get();
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }, "t2");
? ? ? ? t1.start();
? ? ? ? Thread.sleep(1000);
? ? ? ? t2.start();
? ? }
? ? private static void initMyQueue(MyQueue myQueue) {
? ? ? ? myQueue.put("a");
? ? ? ? myQueue.put("b");
? ? ? ? myQueue.put("c");
? ? ? ? myQueue.put("d");
? ? ? ? myQueue.put("e");
? ? ? ? System.out.println("當(dāng)前元素個(gè)數(shù):" + myQueue.size());
? ? }
}
執(zhí)行結(jié)果:
?元素 a 被添加?
?元素 b 被添加?
?元素 c 被添加?
?元素 d 被添加?
?元素 e 被添加?
當(dāng)前元素個(gè)數(shù):5
?元素 a 被消費(fèi)?
?元素 h 被添加?
?元素 b 被消費(fèi)?
?元素 i 被添加?
其他注意事項(xiàng)
(1)wait()和notify()方法要在同步塊或同步方法中調(diào)用,即在調(diào)用前,線程也必須獲得該對(duì)象的對(duì)象級(jí)別鎖。
(2)wait方法是釋放鎖,notify方法是不釋放鎖的;
(3)notify每次喚醒wait等待狀態(tài)的線程都是隨機(jī)的,且每次只喚醒一個(gè);
(4)notifAll每次喚醒wait等待狀態(tài)的線程使之重新競(jìng)爭(zhēng)獲取對(duì)象鎖,優(yōu)先級(jí)最高的那個(gè)線程會(huì)最先執(zhí)行;
(5)當(dāng)線程處于wait()狀態(tài)時(shí),調(diào)用線程對(duì)象的interrupt()方法會(huì)出現(xiàn)InterruptedException異常;
其他知識(shí)點(diǎn)
(1)進(jìn)程間的通信方式:
管道(pipe)、有名管道(named pipe)、信號(hào)量(semophore)、消息隊(duì)列(message queue)、信號(hào)(signal)、共享內(nèi)存(shared memory)、套接字(socket);
(2)線程程間的通信方式:
1、鎖機(jī)制?
1.1 互斥鎖:提供了以排它方式阻止數(shù)據(jù)結(jié)構(gòu)被并發(fā)修改的方法。?
1.2 讀寫鎖:允許多個(gè)線程同時(shí)讀共享數(shù)據(jù),而對(duì)寫操作互斥。?
1.3 條件變量:可以以原子的方式阻塞進(jìn)程,直到某個(gè)特定條件為真為止。
對(duì)條件測(cè)試是在互斥鎖的保護(hù)下進(jìn)行的。條件變量始終與互斥鎖一起使用。
2、信號(hào)量機(jī)制:包括無名線程信號(hào)量與有名線程信號(hào)量?
3、信號(hào)機(jī)制:類似于進(jìn)程間的信號(hào)處理。
線程間通信的主要目的是用于線程同步,所以線程沒有象進(jìn)程通信中用于數(shù)據(jù)交換的通信機(jī)制。
---------------------?
作者:徐劉根?
來源:CSDN?
原文:https://blog.csdn.net/xlgen157387/article/details/78195817?
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!
總結(jié)
以上是生活随笔為你收集整理的Java多线程编程-(4)-线程间通信机制的介绍与使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 眼皮有时单有时双怎么办
- 下一篇: Java多线程编程-(5)-使用Lock