新聞中心
前言
合理的使用多線程能夠更好的利用服務(wù)器資源,一般來講,每個線程內(nèi)部都有自己的上下文,它們之間互不干擾。但是我們有時候需要多個線程之間互相協(xié)作,就需要我們掌握線程的通信方式。

鎖
首先我們先了解一下鎖的概念,之前我們也遇到過,但是沒有細講,今天就把概念理清楚了。在Java多線程中,一把鎖在同一時刻只能被一個線程獲取,其它線程想要獲取它,必須等待該線程釋放鎖,這時候就牽扯到同步的概念了。因為鎖的機制,我們可以使線程可以同步執(zhí)行,下面以打印數(shù)字為例,看下區(qū)別。
- 無鎖下:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
System.out.println("2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}輸出:
2
1
而且每次運行的結(jié)果都是不一樣的。
- 有鎖下:
public static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
輸出:
1
2
可以看到,無論我執(zhí)行幾次結(jié)果都是一樣的,而且執(zhí)行的時候還有等待的效果。
我們這里使用了synchronized關(guān)鍵字,在對象lock上加了一把鎖,只有當(dāng)t1執(zhí)行完釋放掉鎖,t2才能獲取鎖,然后執(zhí)行。
這里我們需要注意的是,synchronized會不斷嘗試去獲取鎖,直到拿到,所以有時候我們程序異常了,記得把鎖釋放掉,不然會不斷消耗服務(wù)器資源的。
wait & notify
我們上節(jié)帶大家了解了wait,notify沒有怎么去講解,現(xiàn)在我們就來說一下。其實這兩者是等待通知機制。
- notify()方法會隨機叫醒一個正在等待的線程。
- notifyAll()會叫醒所有正在等待的線程。
我們還是通過上面的例子給大家演示一下。
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
System.out.println("1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}實際輸出:
2
發(fā)現(xiàn)是先輸出2,然后線程就被堵了,1執(zhí)行不到。大家這里可以猜測一下這個wait的作用是什么。我們大體可以猜到,這個wait其實是做了釋放鎖的操作,調(diào)用之后它進入了等待階段,t2拿到鎖開始執(zhí)行,這時候t1還在等待,所以我們需要喚醒它。
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
System.out.println("1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("2");
try {
Thread.sleep(1000);
// 喚醒當(dāng)前等待的線程
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}輸出:
2
1
發(fā)現(xiàn)正常了,有輸出。這里大家要注意的是,這個機制需要依賴同一個對象鎖,也就是這里的lock對象,底層調(diào)用的wait和notify都是native方法。
public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
信號量
我們也可以通過信號量的方式使線程之間互相協(xié)作,這里給大家介紹一下volatile實現(xiàn)的信號量。
- volatile關(guān)鍵字能夠保證內(nèi)存的可見性,如果用volatile關(guān)鍵字聲明了一個變量,在一個線程里面改變了這個變量的值,那其它線程是立馬可見更改后的值的。
class A {
//private static volatile int num = 0;
private static int num = 0;
static class ThreadA implements Runnable {
@Override
public void run() {
while (num < 5) {
if(num == 4) {
System.out.println("threadA: " + num);
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (num < 5) {
System.out.println("threadB: " + num);
num = num + 1;
}
}
}
}
// 運行
public static void main(String[] args) throws InterruptedException {
new Thread(new A.ThreadA()).start();
Thread.sleep(1000);
new Thread(new A.ThreadB()).start();
}首先這是沒加volatile。
threadB: 0
threadB: 1
threadB: 2
threadB: 3
threadB: 4
加volatile。
threadB: 0
threadB: 1
threadB: 2
threadB: 3
threadB: 4
threadA: 5
我們可以發(fā)現(xiàn)A可以實時看到num值,并且輸出了。
其實我們在使用volatile是需要進行原子操作的,這里只是給大家演示一下,實際中不要這么用。說了這么多,什么場景用呢有時候我們線程有許多個,都需要共享同一資源的時候,使用之前的wait和notify顯然有些麻煩,此時我們就可以使用它了。
Channel
其實我們也可以借助管道實現(xiàn)通信,其實這屬于IO的知識了。這里給大家簡單演示一下,多線程中如何使用,主要借助PipedWriter和PipedReader。
public static void main(String[] args) throws IOException {
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
writer.connect(reader);
Thread t1 = new Thread(() -> {
int rec = 0;
try {
while ((rec = reader.read()) != -1) {
System.out.print("\nt1 接收到 ----->" + (char)rec);
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
writer.write("hello 我是 t2");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}輸出:
t1 接收到 ----->h
t1 接收到 ----->e
t1 接收到 ----->l
t1 接收到 ----->l
t1 接收到 ----->o
t1 接收到 ----->
t1 接收到 ----->我
t1 接收到 ----->是
t1 接收到 ----->
t1 接收到 ----->t
t1 接收到 ----->2
進程已結(jié)束,退出代碼0
ThreadLocal
ThreadLocal是一個本地線程副本變量工具類。內(nèi)部是一個「弱引用」的Map來維護,它為每個線程都創(chuàng)建一個「副本」,每個線程可以訪問自己內(nèi)部的副本變量,最常用的就是set和get方法了,下面給大家演示一下。
public static void main(String[] args) throws InterruptedException {
ThreadLocal local = new ThreadLocal<>();
Thread t1 = new Thread(() -> {
local.set("t1");
System.out.println(local.get());
});
Thread t2 = new Thread(() -> {
local.set("t2");
System.out.println(local.get());
});
t1.start();
t2.start();
} 輸出:
t2
t1
其它方式
其實我們之前講的join(),sleep()...這些其實也是這一部分內(nèi)容,總的來說,它們之間互相協(xié)作,具體用法可以看前面的文章,這里就不一一介紹了。
當(dāng)前名稱:面試官:說一下線程間的通信
瀏覽路徑:http://www.fisionsoft.com.cn/article/dphihgj.html


咨詢
建站咨詢
