傳統的線程通信
當線程在系統內運行時,線程的調度具有一定的透明性,程序通常無法準確控制線程的輪換執行,單Java也提供了一些機制來保證線程協調運行。爲了實現這一機制,可以藉助Object類提供的wait(),notify()和notifyAll()三個方法:
- wait() :導致當前線程等待,直到其他線程調用該同步監視器的notify()方法或notifyAll()方法來喚醒該線程。調用wait()方法的當前線程會釋放該同步監視器的鎖定;
- notify():喚醒在此同步監視器上等待的單個線程,若有多個等待線程,則會隨機喚醒其中一個;
- notify() :喚醒在此同步監視器上等待的所有線程。
演示代碼如下:
Account類:
package com.dalingjia.thead.threadCommunication.tradition;
public class Account {
//賬號編號
private String accountNo;
//賬號餘額
private Double balance;
public Account(String accountNo, Double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public synchronized void draw(double drawAmount) {
try {
//賬戶沒錢,等待
if (!(balance > 0)) {
wait();
} else {
balance -= drawAmount;
System.out.println(Thread.currentThread().getName() + "<-取走:" + drawAmount + " ,餘額:" + balance);
notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存錢
public synchronized void deposit(double depositAmount) {
try {
//賬戶有錢,等待
if (balance > 0) {
wait();
} else {
balance += depositAmount;
System.out.println(Thread.currentThread().getName() + "->存進:" + depositAmount + " ,餘額:" + balance);
notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
取錢線程:
package com.dalingjia.thead.threadCommunication.tradition;
public class DrawThread extends Thread {
private Account account;
private Double amount;
public DrawThread(String name, Account account, Double amount) {
super(name);
this.account = account;
this.amount = amount;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
account.draw(amount);
}
}
}
存錢線程:
package com.dalingjia.thead.threadCommunication.tradition;
/**
* 存錢線程
*/
public class DepositThread extends Thread {
private Account account;
//存錢數量
private Double amount;
public DepositThread(String name, Account account, Double amount) {
super(name);
this.account = account;
this.amount = amount;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
account.deposit(amount);
}
}
}
測試類:
package com.dalingjia.thead.threadCommunication.tradition;
public class Test {
public static void main(String[] args) {
//1,定義一個賬戶
Account account = new Account("thq10000", 0d);
//2,準備target
DepositThread depositThread = new DepositThread("存",account, 100d);
DrawThread drawThread = new DrawThread( "取",account, 100d);
//3,開啓2個線程
depositThread.start();
drawThread.start();
}
}
使用Condition控制線程通信:
如果程序不使用synchronized關鍵字來保證同步,而是直接使用Lock對象來保證同步,則系統中不存在隱式的同步監視器,也就是不使用wait(),notify()和notifyAll()方法進行線程通信,而是使用Condition對象來進行通信,它有如下三個方法:
- await():類似於wait(),導致當前線程等待,知道其他線程調用Condition的signal()或signalAll()來喚醒該線程。
- signal():類似於notify(),隨機喚醒一個線程;
- signalAll():喚醒在此Lock對象上等待的所有線程。
代碼演示如下:
Account類如下,其他類和上面代碼相同:
package com.dalingjia.thead.threadCommunication.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
//使用同步鎖來保證線程同步
private final Lock lock = new ReentrantLock();
//使用condition對象來進行線程通信
private final Condition condition = lock.newCondition();
//賬號編號
private String accountNo;
//賬號餘額
private Double balance;
public Account(String accountNo, Double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public void draw(double drawAmount) {
lock.lock();
try {
//沒錢,等待
if(!(balance>0)) {
condition.await();
}else {
balance -= drawAmount;
System.out.println(Thread.currentThread().getName() + "<-取走:" + drawAmount + " ,餘額:" + balance);
condition.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//使用finally塊來釋放鎖
lock.unlock();
}
}
//存錢
public synchronized void deposit(double depositAmount) {
lock.lock();
try {
//賬戶有錢,等待
if (balance > 0) {
condition.await();
} else {
balance += depositAmount;
System.out.println(Thread.currentThread().getName() + "->存進:" + depositAmount + " ,餘額:" + balance);
condition.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//使用finally塊來釋放鎖
lock.unlock();
}
}
}
使用阻塞隊列(BlockingQueue)來控制線程通信
- BlockingQueue作爲線程同步的工具,具有一個特徵:當生產者線程試圖想BlockingQueue中放入元素時,如果該隊列已滿,則該線程阻塞;當消費者線程試圖從BlockingQueue中取出元素時,如果該隊列已空,則該線程被阻塞。
- BlockingQueue提供如下兩個支持阻塞的方法:
put(E e): 嘗試把e元素放入BlockingQueue中,如果該隊列的元素已滿,則阻塞該線程。
take(): 嘗試從BlockingQueue的頭部取出元素,如果該隊列的元素已空,則阻塞該線程。
BlockingQueue包含的方法之間的對應關係如下:
BlockingQueue代碼演示如下:
生成者:
package com.dalingjia.thead.threadCommunication.BlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Producer extends Thread {
private BlockingQueue<String> bq;
public Producer(String name, BlockingQueue<String> bq) {
super(name);
this.bq = bq;
}
@Override
public void run() {
String[] strings = {"Java", "Spring", "Mysql"};
for (int i = 0; i < 10; i++) {
try {
bq.put(strings[i%3]);
System.out.println(getName() + "生成:" + bq);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消費者:
package com.dalingjia.thead.threadCommunication.BlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Consumer extends Thread {
private BlockingQueue bq ;
public Consumer(String name, BlockingQueue bq) {
super(name);
this.bq = bq;
}
@Override
public void run() {
while (true) {
try {
bq.take();
System.out.println(getName() + "消費:" + bq);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
測試類:
package com.dalingjia.thead.threadCommunication.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Test {
public static void main(String[] args) {
BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
Producer producer = new Producer("生產者->", bq);
Consumer cousumer = new Consumer("消費者<-", bq);
producer.start();
cousumer.start();
}
}
上述測試代碼存在線程安全問題,本測試只爲加深理解BlockingQueue。