摘要:線程通信傳統的線程通信方法概述方法導致當前線程等待,直到其他線程調用該同步監視器的方法或方法來喚醒該線程。運行結果如下線程組和未處理的異常表示線程組,可以對一批線程進行分類管理。對線程組的控制相當于同時控制這批線程。
線程通信 傳統的線程通信
方法概述:
wait方法:導致當前線程等待,直到其他線程調用該同步監視器的notify()方法或notifyAll()方法來喚醒該線程。
wait()方法有三種形式——無時間參數的wait()方法(一直等待,直到其他線程通知); 帶毫秒參數的wait()方法、帶毫秒、毫微妙參數的wait()方法,這2種方法都是等待指定時間后自動蘇醒 調用wait()方法的當前線程會釋放對該同步監視器的鎖定
notify:喚醒在此同步監視器上等待的單個線程。如果所有線程都在此同步監視器上等待,則會隨機選擇喚醒其中一個線程。只有當前線程放棄對該同步監視器的鎖定后(用wait()方法),才可以執行被喚醒的線程
notifyAll:喚醒在此同步監視器上等待的所有線程。只有當前線程放棄對該同步監視器的鎖定后,才能執行喚醒的線程
這三個方法屬于Object類,必須由同步監視器對象來調用,可分成以下兩種情況:
對于使用synchronize修飾的同步方法,因為該類的默認實例(this)就是同步監視器,所以可以在同步方法中直接調用這三個方法
對于使用synchronized修改的同步代碼塊,同步監視器是synchronized后可括號中的對象,所以必須使用括號中的對象調用這3個方法
public class Account { // 封裝賬戶編號、賬戶余額的兩個成員變量 private String accountNo; private double balance; // 標識賬戶中是否已有存款的旗標 private boolean flag = false; public Account(){} // 構造器 public Account(String accountNo , double balance) { this.accountNo = accountNo; this.balance = balance; } // accountNo的setter和getter方法 public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public String getAccountNo() { return this.accountNo; } // 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法, public double getBalance() { return this.balance; } public synchronized void draw(double drawAmount) { try { // 如果flag為假,表明賬戶中還沒有人存錢進去,取錢方法阻塞 if (!flag) { wait(); } else { // 執行取錢 System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount); balance -= drawAmount; System.out.println("賬戶余額為:" + balance); // 將標識賬戶是否已有存款的旗標設為false。 flag = false; // 喚醒其他線程 notifyAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } public synchronized void deposit(double depositAmount) { try { // 如果flag為真,表明賬戶中已有人存錢進去,則存錢方法阻塞 if (flag) //① { wait(); } else { // 執行存款 System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount); balance += depositAmount; System.out.println("賬戶余額為:" + balance); // 將表示賬戶是否已有存款的旗標設為true flag = true; // 喚醒其他線程 notifyAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } // 下面兩個方法根據accountNo來重寫hashCode()和equals()方法 public int hashCode() { return accountNo.hashCode(); } public boolean equals(Object obj) { if(this == obj) return true; if (obj !=null && obj.getClass() == Account.class) { Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; } }使用Condition控制線程通信
直接使用Lock對象來保證同步,則系統中不存在隱式的同步監視器,不能使用wait()、notify()、notifyAll()方法進行線程通信
當使用Lock對象來保證同步時,Java提供一個Condition類來保持協調,使用Condition可以讓那些已經得到Lock對象卻無法繼續執行的線程釋放Lock對象,Condition對象也可以喚醒其他處于等待的線程
Condition將同步監視器方法(wait、notify、notifyAll)分解成截然不同的對象,以便通過將這些對象與Lock對象組合使用,為每個對象提供了多個等待集(wait-set),這種情況下,Lock替代了同步方法和同步代碼塊,Condition替代同步監視器的功能
Condition實例被綁定在一個Lock對象上,要獲得特定的Lock實例的Condition實例,調用Lock對象的newCondition()即可
Condition類方法介紹:
await():類似于隱式同步監視器上的wait方法,導致當前程序等待,直到其他線程調用該Condition的signal()方法和signalAll()方法來喚醒該線程。該await方法有跟多獲取變體:long awaitNanos(long nanosTimeout)、void awaitUninterruptibly()、awaitUntil(Date daadline)等
signal():喚醒在此Lock對象上等待的單個線程,如果所有的線程都在該Lock對象上等待,則會選擇隨機喚醒其中一個線程。只有當前線程放棄對該Lock對象的鎖定后(使用await()方法),才可以喚醒在執行的線程
signalAll():喚醒在此Lock對象上等待的所有線程。只有當前線程放棄對該Lock對象的鎖定后,才可以執行被喚醒的線程
import java.util.concurrent.*; import java.util.concurrent.locks.*; public class Account { // 顯式定義Lock對象 private final Lock lock = new ReentrantLock(); // 獲得指定Lock對象對應的Condition private final Condition cond = lock.newCondition(); // 封裝賬戶編號、賬戶余額的兩個成員變量 private String accountNo; private double balance; // 標識賬戶中是否已有存款的旗標 private boolean flag = false; public Account(){} // 構造器 public Account(String accountNo , double balance) { this.accountNo = accountNo; this.balance = balance; } // accountNo的setter和getter方法 public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public String getAccountNo() { return this.accountNo; } // 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法, public double getBalance() { return this.balance; } public void draw(double drawAmount) { // 加鎖 lock.lock(); try { // 如果flag為假,表明賬戶中還沒有人存錢進去,取錢方法阻塞 if (!flag) { cond.await(); } else { // 執行取錢 System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount); balance -= drawAmount; System.out.println("賬戶余額為:" + balance); // 將標識賬戶是否已有存款的旗標設為false。 flag = false; // 喚醒其他線程 cond.signalAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } // 使用finally塊來釋放鎖 finally { lock.unlock(); } } public void deposit(double depositAmount) { lock.lock(); try { // 如果flag為真,表明賬戶中已有人存錢進去,則存錢方法阻塞 if (flag) // ① { cond.await(); } else { // 執行存款 System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount); balance += depositAmount; System.out.println("賬戶余額為:" + balance); // 將表示賬戶是否已有存款的旗標設為true flag = true; // 喚醒其他線程 cond.signalAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } // 使用finally塊來釋放鎖 finally { lock.unlock(); } } // 下面兩個方法根據accountNo來重寫hashCode()和equals()方法 public int hashCode() { return accountNo.hashCode(); } public boolean equals(Object obj) { if(this == obj) return true; if (obj !=null && obj.getClass() == Account.class) { Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; } }使用阻塞隊列(BlockingQueue)控制線程通信
BlockingQueue具有一個特征:當生產者線程試圖向BlockingQueue中放入元素時,如果該隊列已滿,則線程被阻塞;但消費者線程試圖從BlockingQueue中取出元素時,如果隊列已空,則該線程阻塞
程序的兩個線程通過交替向BlockingQueue中放入元素、取出元素,即可很好地控制線程的通信
BlockingQueue提供如下兩個支持阻塞的方法:
put(E e):嘗試把E元素放入BlockingQueue中,如果該隊列的元素已滿,則阻塞該線程
take():嘗試從BlockingQueue的頭部取出元素,如果該隊列的元素已空,則阻塞該線程
BlockingQueue繼承了Queue接口,當然也可以使用Queue接口中的方法,這些方法歸納起來可以分為如下三組:
在隊列尾部插入元素,包括add(E e)、offer(E e)和put(E e)方法,當該隊列已滿時,這三個方法分別會拋出異常、返回false、阻塞隊列
在隊列頭部刪除并返回刪除的元素。包括remove()、poll()和take()方法,當該隊列已空時,這三個方法分別會拋出異常、返回false、阻塞隊列
在隊列頭部取出但不刪除元素。包括element()和peek()方法,當隊列已空時,這兩個方法分別拋出異常、返回false
- | 拋出異常 | 不同返回值 | 阻塞線程 | 指定超時時差 |
---|---|---|---|---|
隊尾插入元素 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
隊頭刪除元素 | remove() | poll() | take() | poll(time, unit) |
獲取、不刪除元素 | element() | peek() | 無 | 無 |
BlockingQueue的5個實現類:
ArrayBlockingQueue:基于數組實現的BlockingQueue隊列
LinkedBlockingQueue:基于鏈表實現的BlockingQueue隊列
PriorityBlockingQueue:它并不是標準的阻塞隊列,當調用remove()、poll()、take()等方法取出元素時,并不是取出隊列中存在時間最長的元素,而是隊列中最小的元素。判斷元素的大小可根據元素(實現Comparable接口)的本身大小來自然排序,也可以使用Comparator進行定制排序
SynchronousQueue:同步隊列,對該隊列的存、取操作必須交替進行
DelayQueue:它是一個特殊的BlockingQueue,底層基于PriorityBlockingQueue實現,DelayQueue要求集合元素都實現Delay接口(該接口里只有一個long getDelay()方法),DelayQueue根據集合元素的getDelay()方法的返回值進行排序
import java.util.concurrent.*; public class BlockingQueueTest { public static void main(String[] args) throws Exception { // 定義一個長度為2的阻塞隊列 BlockingQueuebq = new ArrayBlockingQueue<>(2); bq.put("Java"); // 與bq.add("Java"、bq.offer("Java")相同 bq.put("Java"); // 與bq.add("Java"、bq.offer("Java")相同 bq.put("Java"); // ① 阻塞線程。 } }
利用BlockingQueue實現線程通信
import java.util.concurrent.*; class Producer extends Thread { private BlockingQueuebq; public Producer(BlockingQueue bq) { this.bq = bq; } public void run() { String[] strArr = new String[] { "Java", "Struts", "Spring" }; for (int i = 0 ; i < 999999999 ; i++ ) { System.out.println(getName() + "生產者準備生產集合元素!"); try { Thread.sleep(200); // 嘗試放入元素,如果隊列已滿,線程被阻塞 bq.put(strArr[i % 3]); } catch (Exception ex){ex.printStackTrace();} System.out.println(getName() + "生產完成:" + bq); } } } class Consumer extends Thread { private BlockingQueue bq; public Consumer(BlockingQueue bq) { this.bq = bq; } public void run() { while(true) { System.out.println(getName() + "消費者準備消費集合元素!"); try { Thread.sleep(200); // 嘗試取出元素,如果隊列已空,線程被阻塞 bq.take(); } catch (Exception ex){ex.printStackTrace();} System.out.println(getName() + "消費完成:" + bq); } } } public class BlockingQueueTest2 { public static void main(String[] args) { // 創建一個容量為1的BlockingQueue BlockingQueue bq = new ArrayBlockingQueue<>(1); // 啟動3條生產者線程 new Producer(bq).start(); new Producer(bq).start(); new Producer(bq).start(); // 啟動一條消費者線程 new Consumer(bq).start(); } }
程序啟動3個生產者線程向BlockingQueue集合放入元素,啟動1個消費者線程從BlockingQueue集合取出元素。本程序的BlockingQueue集合容量為1,因此3個生產者線程無法連續放入元素,必須等待消費者線程取出一個元素后,3個生產者線程的其中一個才能放入元素。運行結果如下:
ThreadGroup表示線程組,可以對一批線程進行分類管理。對線程組的控制相當于同時控制這批線程。默認情況下,子線程和創建它的父線程屬于同一個線程組
一旦某個線程加入了指定線程組之后,該線程將一直屬于該線程組,直到該線程死亡,線程運行中不能改變它所屬的線程組
Thread類提供了如下幾個構造器來設置新創建的線程屬于哪個線程組:
Thread(ThreadGroup group, Runnable target):以target的run()方法作為線程執行體創建新線程,屬于group線程組
Thread(ThreadGroup group, Runnable target, String name):以target的run()方法作為線程執行體創建新線程,屬于group線程組,且線程名為name
Thread(ThreadGroup group, String name):創建新線程,新線程名為name,屬于group線程組
因為中途不可改變線程所屬的線程組,所以Thread類沒有setThreadGroup()方法,但提供getThreadGroup()方法來返回該線程所屬的線程組,返回值是ThreadGroup對象
ThreadGroup類的兩個構造器:
ThreadGroup(String name):以指定的線程組名字來創建新的線程組
ThreadGroup(ThreadGroup parent, String name):以指定的名字、指定的父線程組創建新的線程組
ThreadGroup類操作整個線程組里的所有線程的幾個常用方法:
int activeCount():返回此線程組中活動線程的數目
interrupt():中斷此線程組中的所有線程
isDaemon():判斷該線程組是否是后臺線程組
setDaemon(boolean daemon):把該線程組設置成后臺線程組。后臺線程組具有一個特征,當后臺線程組的最后一個線程執行結束或最后一個線程被銷毀后,后臺線程組將自動銷毀
setMaxPriority(int pri):設置線程組的最高優先級
class MyThread extends Thread { // 提供指定線程名的構造器 public MyThread(String name) { super(name); } // 提供指定線程名、線程組的構造器 public MyThread(ThreadGroup group, String name) { super(group, name); } public void run() { for (int i = 0; i < 20 ; i++ ) { System.out.println(getName() + " 線程的i變量" + i); } } } public class ThreadGroupTest { public static void main(String[] args) { // 獲取主線程所在的線程組,這是所有線程默認的線程組 ThreadGroup mainGroup = Thread.currentThread().getThreadGroup(); System.out.println("主線程組的名字:" + mainGroup.getName()); System.out.println("主線程組是否是后臺線程組:" + mainGroup.isDaemon()); new MyThread("主線程組的線程").start(); ThreadGroup tg = new ThreadGroup("新線程組"); tg.setDaemon(true); System.out.println("tg線程組是否是后臺線程組:" + tg.isDaemon()); MyThread tt = new MyThread(tg, "tg組的線程甲"); tt.start(); new MyThread(tg, "tg組的線程乙").start(); } }
void uncaughtException(Thread t, Throwable e):該方法可以處理該線程組內的任意線程所拋出的未處理異常
JVM在結束線程前會自動查找是否有對應的Thread.UncaughtExceptionHandler對象(該類為是Thread的一個靜態內部接口),如果找到該處理器對象,會調用該對象的uncaughtException(Thread t, Throwable e)來處理該異常。t:表示出現異常的線程,e表示該線程拋出的異常
Thread類提供兩個方法設置異常處理器:
static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHanlder eh):為該線程類的所有線程實例設置默認的異常處理器
static setUncaughtExceptionHandler(Thread.UncaughtExceptionHanlder eh):為指定的線程實例設置異常處理器
線程組處理異常的默認流程:
如果該線程組有父線程組,則調用父線程組的uncaughtException()來處理異常
如果該線程對象所屬線程類有有默認異常處理器(由setDefaultUncaughtExceptionHandler()方法設置的異常處理器),那么就調用該異常處理器來處理該異常
如果該異常對象是ThreadDeath的對象,則不做任何處理;否則,將異常跟蹤棧的信息打印到System.err錯誤輸出流,并結束該線程
class MyExHandler implements Thread.UncaughtExceptionHandler { // 實現uncaughtException方法,該方法將處理線程的未處理異常 public void uncaughtException(Thread t, Throwable e) { System.out.println(t + " 線程出現了異常:" + e); } } public class ExHandler { public static void main(String[] args) { // 設置主線程的異常處理器 Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler()); int a = 23 / 0; // ① System.out.println("程序正常結束!"); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66717.html
摘要:本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,的使用,定時器,單例模式,以及線程狀態與線程組。源碼采用構建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,lock的使用,定時器,單例模式,以及線程狀態與線程組。 寫在前面 花了一周時...
摘要:創建線程的方式方式一將類聲明為的子類。將該線程標記為守護線程或用戶線程。其中方法隱含的線程為父線程。恢復線程,已過時。等待該線程銷毀終止。更多的使當前線程在鎖存器倒計數至零之前一直等待,除非線 知識體系圖: showImg(https://segmentfault.com/img/bVbef6v?w=1280&h=960); 1、線程是什么? 線程是進程中獨立運行的子任務。 2、創建線...
摘要:并發編程導論是對于分布式計算并發編程系列的總結與歸納。并發編程導論隨著硬件性能的迅猛發展與大數據時代的來臨,并發編程日益成為編程中不可忽略的重要組成部分。并發編程復興的主要驅動力來自于所謂的多核危機。 并發編程導論是對于分布式計算-并發編程 https://url.wx-coder.cn/Yagu8 系列的總結與歸納。歡迎關注公眾號:某熊的技術之路。 showImg(https://...
閱讀 3233·2021-09-07 10:10
閱讀 3579·2019-08-30 15:44
閱讀 2578·2019-08-30 15:44
閱讀 2982·2019-08-29 15:11
閱讀 2219·2019-08-28 18:26
閱讀 2745·2019-08-26 12:21
閱讀 1113·2019-08-23 16:12
閱讀 3010·2019-08-23 14:57