軟件都是由不同的模塊組成一個系統(tǒng)啦粹,從而模塊與模塊間的通信,線程與線程間通信是經(jīng)常碰到的。下面我們介紹了關于線程間通信的幾種技術方案
1.wait/notify
1.1不使用wait/notify的代碼演示
public class TpadTask {
private List<Integer> list = new ArrayList<>();
public void doAdd() {
Integer num = new Random().nextInt(100);
list.add(num);
}
public int size() {
return list.size();
}
}
public class ThreadA extends Thread {
private TpadTask task;
public ThreadA(TpadTask task) {
super();
this.task = task;
}
@Override
public void run() {
for(int i=0;i<10;i++){
task.doAdd();
System.out.println(i);
}
}
}
public class ThreadB extends Thread {
private TpadTask task;
public ThreadB(TpadTask task) {
super();
this.task = task;
}
@Override
public void run() {
try {
while (true) {
if (task.size() == 5) {
System.out.println("已達到5,b線程要退出");
throw new InterruptedException();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) {
TpadTask task = new TpadTask();
ThreadA a = new ThreadA(task);
ThreadB b = new ThreadB(task);
a.start();
b.start();
}
}
說明:線程A是進行對list進行添加數(shù)據(jù),線程B是對list的大小進行監(jiān)控赋访,如果達到5,則主動退出缓待,拋出異常蚓耽。
- 結(jié)果
0
1
2
3
4
已達到5,b線程要退出
5
6
7
8
9
java.lang.InterruptedException
at communication.ThreadB.run(ThreadB.java:21)
缺點:
- 線程B需要不停的循環(huán)來判斷定條件旋炒,這樣會浪費CPU資源步悠。
1.2 wait/notify機制
- 1.wait()
wait()的作用是使當前執(zhí)行的線程進行等待,wait()方法是Object類的方法。該方法是將當前線程置入預執(zhí)行隊列瘫镇,并在wait代碼處停止執(zhí)行鼎兽,直到接收到通知或被中斷執(zhí)行答姥。
在調(diào)用wait()之前,線程必須獲取該對象的對象級別鎖谚咬,即只能在同步方法或者同步代碼塊中調(diào)用wait()方法鹦付。
在執(zhí)行wait()方法后,當前線程釋放鎖序宦。從wait()返回前睁壁,線程與其他線程競爭重新獲取鎖。如果調(diào)用wait()沒有持有鎖互捌,則會拋出異常潘明。
- wait方法的注釋
The current thread must own this object's monitor.
- 2.notify()
notify()的作用是使停止的線程繼續(xù)運行。
在調(diào)用notify()之前秕噪,線程必須獲取該對象的對象級別鎖钳降,也只能在同步方法或者同步代碼塊調(diào)用
執(zhí)行notify()方法后,當前線程不會馬上釋放該對象鎖腌巾。呈現(xiàn)wait的線程也不會馬上獲取到該對象鎖遂填。要等到執(zhí)行notify()方法的線程將程序執(zhí)行完。也就是退出synchronized代碼塊后澈蝙,當前線程才會釋放該對象鎖吓坚。
3.改造上述代碼
TpadTask代碼不變
ThreadA代碼改造
public class ThreadA extends Thread {
private TpadTask task;
private Object lock;
public ThreadA(TpadTask task, Object obj) {
super();
this.task = task;
this.lock = obj;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
synchronized (lock) {
task.doAdd();
System.out.println(i);
if (task.size() == 5) {
lock.wait();
System.out.println("接收到信息!5朴礁击!");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- ThreadB
public class ThreadB extends Thread {
private TpadTask task;
private Object lock;
public ThreadB(TpadTask task, Object obj) {
super();
this.task = task;
this.lock = obj;
}
@Override
public void run() {
try {
synchronized (lock) {
if (task.size() == 5) {
lock.notify();
System.out.println("已發(fā)出信號!6涸亍哆窿!");
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
- 測試
public class Client {
public static void main(String[] args) throws InterruptedException {
TpadTask task = new TpadTask();
Object lock = new Object();
ThreadA a = new ThreadA(task, lock);
a.start();
ThreadB b = new ThreadB(task, lock);
b.start();
}
}
- 結(jié)果
0
1
2
3
4
已發(fā)出信號!@髡濉挚躯!
接收到信息!2粱唷码荔!
5
6
7
8
9
- 4.線程狀態(tài)切換
- 5.wait(long timeout)
等待一段時間,看是否有線程對鎖進行喚醒感挥。如果超過這個時間缩搅,則自動喚醒。
2.通過管道流進行通信(pipeStream)
jdk提供了4個類用于管道流通信链快。java.io.PipedInputStream誉己、java.io.PipedOutputStream眉尸、java.io.PipedReader域蜗、java.io.PipedWriter
它們的作用是讓多線程可以通過管道進行線程間的通訊巨双。在使用管道通信時,必須將PipedOutputStream和PipedInputStream配套使用霉祸。
大致的流程是:我們在線程A中向PipedOutputStream中寫入數(shù)據(jù)筑累,這些數(shù)據(jù)會自動的發(fā)送到與PipedOutputStream對應的PipedInputStream中,進而存儲在PipedInputStream的緩沖中丝蹭;此時慢宗,線程B通過讀取PipedInputStream中的數(shù)據(jù)。就可以實現(xiàn)奔穿,線程A和線程B的通信镜沽。
PipedOutputStream、PipedInputStream代碼演示
新建生產(chǎn)消息線程
public class Producer extends Thread {
private PipedOutputStream pos;
public Producer(PipedOutputStream pos) {
this.pos = pos;
}
@Override
public void run() {
super.run();
try {
pos.write("Hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 新建接收消息線程
public class Consumer extends Thread {
private PipedInputStream pis;
public Consumer(PipedInputStream pis) {
this.pis = pis;
}
@Override
public void run() {
super.run();
byte[] b = new byte[100]; // 將數(shù)據(jù)保存在byte數(shù)組中
try {
int len = pis.read(b); // 從數(shù)組中得到實際大小贱田。
System.out.println(String.format("接收到信號:%s",new String(b, 0, len)));
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 測試
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream();
try {
pos.connect(pis);// 連接管道
new Producer(pos).start();// 啟動線程
new Consumer(pis).start();// 啟動線程
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 結(jié)果
接收到信號:Hello
pos.connect(pis)作用是將“管道輸入流”和“管道輸出流”關聯(lián)起來缅茉。查看PipedWriter.java和PipedReader.java中connect()的源碼;我們知道 out.connect(in); 等價于 in.connect(out)男摧。當然JDK還支持管道通信字符流蔬墩。
3.Join
主線程創(chuàng)建并啟動子線程,如果子線程需要消耗大量的資源耗拓,主線程往往早于子線程執(zhí)行完畢拇颅。而主線程需要等待子線程的結(jié)果,這時候使用join方法乔询。
- 例子
public class CustomerThread extends Thread {
public CustomerThread(){
super();
}
@Override
public void run() {
System.out.println("我是子線程代碼");
}
}
public class Client {
public static void main(String[] args) throws InterruptedException {
CustomerThread a=new CustomerThread();
a.start();
System.out.println("我是主線程代碼");
}
}
- 輸出
我是主線程代碼
我是子線程代碼
主線程已經(jīng)執(zhí)行完畢了樟插,才執(zhí)行子線程,如果需要用到子線程的結(jié)果哥谷。那么只需要加一句join()岸夯。
public class Client {
public static void main(String[] args) throws InterruptedException {
CustomerThread a=new CustomerThread();
a.start();
a.join();//join語句
System.out.println("我是主線程代碼");
}
}
- 結(jié)果
我是子線程代碼
我是主線程代碼
join的方法注釋是:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*/
while (isAlive()) {
wait(0);
}
使得所屬的線程對象正常的執(zhí)行,使得當前的線程進行無線等待们妥,直到所屬線程銷毀后猜扮,再執(zhí)行當前線程。
在join過程中监婶,如果當前線程對象被中斷旅赢,則當前線程對象拋出異常。
join(long)中的參數(shù)是等待的時間惑惶。例如join(2000)煮盼,只等等2秒子線程的執(zhí)行時間。超過2秒自動執(zhí)行主線程代碼带污。
3.1join(long)與sleep(long)的區(qū)別
join內(nèi)部使用的是wait(long)來實現(xiàn)的僵控。所以join()具有釋放鎖的特性。當前線程的鎖被釋放鱼冀,那么其他線程就可以調(diào)用此線程的同步方法了报破。
Thread.sleep(long)方法卻不釋放鎖悠就。
4.ThreadLocal
ThreadLocal并不是一個Thread,而是Thread的局部變量充易,也許把它命名為ThreadLocalVariable更容易讓人理解一些梗脾。
ThreadLocal是解決每個線程綁定屬于自己的值。
ThreadLocal是解決線程安全問題一個很好的思路盹靴,它通過為每個線程提供一個獨立的變量副本炸茧。解決了變量并發(fā)訪問的沖突問題。在很多情況下稿静,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單梭冠,更方便,且結(jié)果程序擁有更高的并發(fā)性改备。
代碼演示
新建ThreadLocal公用類
public class ThreadLocalVariable {
public static ThreadLocal threadLocalVariable = new ThreadLocal();
}
- 新建ThreadA
public class ThreadA extends Thread {
@Override
public void run() {
try {
for(int i=0;i<5;i++){
ThreadLocalVariable.threadLocalVariable.set(String.format("ThreadA:%s",i));
System.out.println(ThreadLocalVariable.threadLocalVariable.get());
}
}
catch (Exception ex){
ex.printStackTrace();
}
}
}
- 新建ThreadB
public class ThreadB extends Thread {
@Override
public void run() {
try {
for(int i=0;i<5;i++){
ThreadLocalVariable.threadLocalVariable.set(String.format("ThreadB:%s",i));
System.out.println(ThreadLocalVariable.threadLocalVariable.get());
}
}
catch (Exception ex){
ex.printStackTrace();
}
}
}
- Client
public class Client {
public static void main(String[] args) {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
ThreadLocalVariable.threadLocalVariable.set("a");
System.out.println(ThreadLocalVariable.threadLocalVariable.get());
}
}
- 結(jié)果
a
ThreadA:0
ThreadA:1
ThreadA:2
ThreadA:3
ThreadA:4
ThreadB:0
ThreadB:1
ThreadB:2
ThreadB:3
ThreadB:4
每個線程都對threadLocalVariable進行設置值妈嘹,但是取值的時候,都是取的各自的設置的值绍妨。
沒賦值前返回的值是null
protected T initialValue() {
return null;
}
4.1InheritableThreadLocal
該類可以讓子線程從父線程中取出值润脸。而子線程從父類繼承的值可以在子線程進行對值得修改