一、傳統的線程通信
-
要藉助於Object類提供的wait()、notift()、notifyAll(),這三個方法都要由同步監視器來調用,分下面兩種情況:
- synchronized 修飾的同步方法,同步監視器就是該類的默認實例(this),所以可以直接在同步代碼塊中調用這三個方法
- synchronized 修飾的同步代碼塊,同步監視器是synchronized 括號中的對象,所以要用對象調用這三個方法。
-
wait():導致線程等待,直到其他線程調用該**同步監視器的notify()/motifyAll()方法
-
notify:喚醒此同步監視器上的單個線程,如果所有線程都在此同步監視器下等待,則隨機喚醒一個,只有當前線程放棄對該同步監視器的鎖定後(調用wait()),纔可以執行被喚醒的線程
-
notifyAll():喚醒此同步監聽器上的所有等待的線程,只有當前線程放棄對該同步監視器的鎖定後(調用wait()),纔可以執行被喚醒的線程
public class Account {
// 銀行賬戶
private String accountNo;
//餘額
private int balance;
// 是否有錢
private boolean flag = false;
public Account(String accountNo, int balance) {
this.accountNo = accountNo;
this.balance = balance;
}
/**
* 取錢
*/
public synchronized void draw(int monny) throws InterruptedException {
if (!flag){
// 如果沒有錢可取,就等待
wait();
}else {
if (balance>=monny){
Log.e("testthread",Thread.currentThread().getName()+"取錢成功:"+monny);
balance-=monny;
Log.e("testthread","餘額:"+balance);
flag = false;
// 喚醒等待的線程
notifyAll();
}else {
Log.e("testthread",Thread.currentThread().getName()+"取錢失敗,餘額不足:"+balance);
}
}
}
/**
* 存錢
*/
public synchronized void deposit(int monny) throws InterruptedException {
if (flag){
//有錢就等着
wait();
}else {
Log.e("testthread",Thread.currentThread().getName()+"存錢成功:"+monny);
balance +=monny;
Log.e("testthread","餘額:"+balance);
flag = true;
// 喚醒等待的線程
notifyAll();
}
}
}
// 取錢的線程
public class DrawThread extends Thread {
// 取錢的賬戶
private Account account;
// 取錢的金額
private int monny;
DrawThread(String name,Account account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
account.draw(monny);
}
}
}
//存錢的線程
public class DepositThread extends Thread {
private Account account;
private int monny;
DepositThread(String name,Account account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
account.deposit(monny);
}
}
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Account account = new Account("admin",0);
new DrawThread("取錢A",account,800).start();
new DepositThread("存錢A",account,800).start();
new DepositThread("存錢B",account,800).start();
new DepositThread("存錢C",account,800).start();
}
});
二、使用Condition控制線程通信
- 當使用Lock對象來保證同步的時候,可以使用Condition來保持協調,讓那些持有Lock卻無法繼續執行的線程,釋放Lock對象;也可以喚醒其他處於等待的線程。
- 此時,Lock代替了同步方法或同步代碼塊的作用,Condition代替了同步監視器的作用
- Condition被綁在一個Lock對象上
- 要獲得特點Lock對象的Condition,就要調用Lock.newCondition()
- Condition提供了三個方法:
- await():導致線程等到,知道Condition的signal、signalAll來喚醒
- signal:喚醒此Lock對象上等待的單個線程,如果所有線程都在等待,則隨機喚醒一個,只有當前線程放棄對Lock對象的鎖定(調用await())後,纔可以執行被喚醒的線程
- signalAll:喚醒此Lock對象上等待的所有線程,只有當前線程放棄對Lock對象的鎖定(調用await())後,纔可以執行被喚醒的線程
public class ConditionAccount {
private String account;
private int balance;
// 是否有錢
private boolean flag;
// 顯示定義一個LOCK對象
private ReentrantLock lock = new ReentrantLock();
// 定義特定Lock對象的Condition對象
Condition condition = lock.newCondition();
/**
* 取錢
* @param monny
*/
public void draw(int monny) throws InterruptedException {
lock.lock();
try {
if (!flag){
//沒有錢,就等着
condition.await();
}else {
if (balance >= monny){
Log.e("testthread",Thread.currentThread().getName()+"取錢成功:"+monny);
balance -=monny;
Log.e("testthread","餘額:"+balance);
flag = false;
// 喚醒其他線程
condition.signalAll();
}
}
}finally {
lock.unlock();
}
}
/**
* 存錢
* @param monny
*/
public void deposit(int monny) throws InterruptedException {
lock.lock();
try {
if (flag){
//有錢,就等着
condition.await();
}else {
Log.e("testthread",Thread.currentThread().getName()+"存錢成功:"+monny);
balance +=monny;
Log.e("testthread","餘額:"+balance);
flag = true;
// 喚醒其他線程
condition.signalAll();
}
}finally {
lock.unlock();
}
}
}
// 存錢的線程
public class DepositThread extends Thread {
private ConditionAccount account;
private int monny;
DepositThread(String name,ConditionAccount account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
try {
account.deposit(monny);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 取錢的類
public class DrawThread extends Thread {
// 取錢的賬戶
private ConditionAccount account;
// 取錢的金額
private int monny;
DrawThread(String name,ConditionAccount account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
try {
account.draw(monny);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ConditionAccount account = new ConditionAccount("admin",0);
new DrawThread("取錢A",account,800).start();
new DepositThread("存錢A",account,800).start();
new DepositThread("存錢B",account,800).start();
new DepositThread("存錢C",account,800).start();
}
});
三、使用阻塞隊列(BlockingQueue)控制線程通信
- BlockingQueue這個接口是Queue的子接口,但是它並不是作爲容器,而是用於線程同步的工具
- 特徵:
- 當生產者線程試圖向BlockingQueue中插入元素,但是該隊列已經滿了,則會阻塞
- 當消費者線程試圖向BlockingQueue中取出元素,但是該隊列已經空了,則會阻塞
方法:
拋出異常 | 不同返回值 | 阻塞線程 | 指定超時時長 | |
---|---|---|---|---|
隊尾插入元素 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
隊頭刪除元素 | remove() | poll() | take() | poll(time,unit) |
獲取,不刪除元素 | element() | pick() | 無 | 無 |
實現類:
-
DelayQueue< E extends Delayed>:
—>基於 PriorityBlockingQueue 實現的,要求集合元素都實現Delay接口 -
LinkedBlockingQueue< E>:
–>基於鏈表實現的BlockingQueue隊列 -
ArrayBlockingQueue< E>
-
Synchronous< E>
–>同步隊列,對該隊列的存取操作必須交替進行 -
PriorityBlockingQueue< E>
–>並不是標準的阻塞隊列,用remove()、take()、poll()取出元素時,並不是取出隊列中存在時間最長的元素,而是隊列中最小的元素
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String> (2);
// 在沒滿時,這裏存入元素,用add()、offer()、Put()都可以
queue.add("android");
queue.add("android");
// 會報錯:java.lang.IllegalStateException: Queue full
//queue.add("android");
//會返回false
//queue.offer("android");
// 會阻塞線程:程序無響應
try {
queue.put("android");
} catch (InterruptedException e) {
e.printStackTrace();
Log.e("testqueue","阻塞:"+e.getMessage());
}
}
});
// 生產者
public class Produce extends Thread{
private BlockingDeque<String> queue;
Produce(BlockingDeque<String> queue){
this.queue = queue;
}
String[] array = new String[]{
"AA","BB","CC"
};
@Override
public void run() {
super.run();
for (int i=0;i<1000;i++){
Log.e("testthread",getName()+"準備生產");
try {
Thread.sleep(200);
// 存入元素
queue.put(array[i%3]);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("testthread",getName()+" 生產完成"+queue);
}
}
}
// 消費者
public class Consume extends Thread{
private BlockingDeque<String> queue;
Consume(BlockingDeque<String> queue){
this.queue = queue;
}
@Override
public void run() {
super.run();
for (int i=0;i<1000;i++){
Log.e("testthread",getName()+" 準備消費");
try {
Thread.sleep(200);
// 存入元素
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("testthread",getName()+" 消費完成"+queue);
}
}
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BlockingDeque<String> queue = new LinkedBlockingDeque<>(1);
//啓動三個生產現場
new Produce(queue).start();
new Produce(queue).start();
new Produce(queue).start();
//啓動一個消費線程
new Consume(queue).start();
}
});
### 四、線程組和未處理的異常
- ThreadGroup:線程組,可以對一批線程進行分類管理
- 用戶創建的所有線程都屬於線程組,如果沒有顯示指定線程組,則屬於默認的線程組,默認情況下和父線程在同一個線程組
- 一旦線程加入某線程組,知道她死亡,都會一直屬於這個線程組,運行過程組不能更改線程組
設置線程屬於哪一個線程組
- public Thread(ThreadGroup group, Runnable target)
- public Thread(ThreadGroup group, String name)
- public Thread(ThreadGroup group, Runnable target, String name)
ThreadGroup的構造方法:
- public ThreadGroup(String name)
- public ThreadGroup(ThreadGroup parent, String name)
ThreadGroup提供的方法:
- String getName() 獲取線程組的名字
- boolean isDaemon():是否是後臺線程
- void setDaemon(boolean daemon):設置爲後臺線程
- void interrupt():中斷此線程組中的所有線程
- int activeCount():返回活動線程的數目
- void setMaxPriority(int pri):設置最高優先級
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ThreadGroup maingroup = Thread.currentThread().getThreadGroup();
//主線程組:main
Log.e("testqueue","主線程組:"+maingroup.getName());
//創建一個新的線程組
ThreadGroup newgroup = new ThreadGroup("新線程組");
newgroup.setDaemon(true);
Log.e("testqueue",maingroup.getName()+":"+newgroup.isDaemon());
}
});