線程開始運行,擁有自己的棧空間,那多個線程如何相互配合完成工作,這就涉及到了線程間的通信。
線程通信是使線程間能夠互相發送信號,是使線程能夠等待其他線程的信號。比如線程 A 在執行到某個條件通知線程 B 執行某個操作
一、共享內存機制
(1)同步–synchronized
線程同步是線程之間按照⼀定的順序執⾏,可以使⽤鎖來實現達到線程同步,也就是在需要同步的代碼塊里加上關鍵字synchronized 。因爲⼀個鎖同⼀時間只能被⼀個線程持有。
關鍵字synchronized可以修飾方法或者以同步塊,它主要確保多個線程在同一個時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性。
這種方式,線程需要不斷地去嘗試獲得鎖,如果失敗了,再繼續嘗試。這可能會耗費服務器資源。
基於synchronized實現線程A和線程B通信
線程A執行完,再讓線程B執行,使用對象鎖實現
public class ObjectLock {
private static Object lock = new Object();
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println("ThreadA " + i);
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println("ThreadB " + i);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new ThreadA()) .start();
Thread.sleep(10);// 這裏讓線程睡10毫秒,可確保A先獲得鎖
System.out.println("---------------------");
new Thread(new ThreadB()).start();
}
}
運行結果:
線程A和線程B需要訪問同一個對象lock,誰獲得鎖,誰就先執行,這裏控制的是讓線程A先執行(Thread.sleep(10)爲的就是A先獲得鎖)。線程B要等線程A執行完再執行,所以是同步的,這就實現了線程間的通信
(2)信號量 --volatile
Java支持多個線程同時訪問一個對象或者對象的成員變量,由於每個線程可以擁有這個變量的拷貝,所以程序在執行過程中,一個線程看到的變量並不一定是最新的。
關鍵字volatile可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從共享內存中獲取,而對它的改變必須同步刷新回共享內存,它能保證所有線程對變量訪問的可見性。
volitile關鍵字能夠保證內存的可⻅性,如果⽤volitile關鍵字聲明瞭⼀個變量,在⼀個線程⾥⾯改變了這個變量的值,那其它線程是⽴⻢可⻅更改後的值的。
基於volatile關鍵字實現線程A和線程B的通信
線程A輸出0,然後線程B輸出1,再然後線程A輸出2…
public class Signal {
private static volatile int signal = 0;
static class ThreadA implements Runnable {
@Override
public void run() {
while (signal < 5) {
if (signal % 2 == 0) {
System.out.println("threadA: " + signal);
synchronized (this) {
signal++;
}
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (signal < 5) {
if (signal % 2 == 1) {
System.out.println("threadB: " + signal);
synchronized (this) {
signal = signal + 1;
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new ThreadA()).start();
Thread.sleep(10);
new Thread(new ThreadB()).start();
}
}
運行結果
volatile 變量需要進⾏原⼦操作。 signal++ 並不是⼀個原⼦操
作,所以我們需要使⽤ synchronized 給它“上鎖”
關於synchronized與volatile ,synchronized主要做的是多線程順序執行,也就是同一個時間只有一個線程在執行,線程A執行完了再讓線程B執行,volatile主要做的是讓多線程間共享的變量保證一致,也就是線程A對變量操作了,線程B對變量操作時是知道線程A對變量的操作的,是在線程A操作後的變量上進行操作。
二、等待/通知機制(wait/notify)
一個線程修改了一個對象的值,而另一個線程感知到了變化,然後進行相應的操作,整個過程開始於一個線程,而最終執行又是另一個線程
等待/通知機制使⽤的是使⽤同⼀個對象鎖,如果你兩個線程使
⽤的是不同的對象鎖,那它們之間是不能⽤等待/通知機制通信的。
等待/通知的相關方法
方法名稱 | 含義 |
---|---|
notify() | 通知一個對象上等待的線程,使其從wait()方法返回,而返回的前提是該線程獲取到了對象的鎖 |
notifyAll() | 通知所有等待在該對象上的線程 |
wait() | 調用該方法的線程進入WAITING狀態,只有等待另外線程的通知或被中斷纔會返回,調用wait()方法後,會釋放對象的鎖 |
wait(long) | 超時等待一段時間,沒有通知就超時返回,參數時間是毫秒 |
wait(long,int) | 對於超時時間更細粒度的控制,可達到納秒 |
notify()和notifyAll()的區別:
notify()⽅法會隨機叫醒⼀個正在等待的線程,⽽notifyAll()會叫醒所有正在等待的線程。
⼀個鎖同⼀時刻只能被⼀個線程持有,lock.wait() 讓⾃⼰進⼊等待狀態,會釋放鎖
lock.notify() 叫醒⼀個正在等待的線程,但並沒有釋放鎖 lock。
基於Object 類的 wait() ⽅法和 notify() ⽅法實現
線程A執行完,線程B執行,再線程A執行…
public class WaitNotify {
private static Object lock = new Object();
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("ThreadA: " + i);
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("ThreadB: " + i);
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new ThreadA()).start();
Thread.sleep(10);
new Thread(new ThreadB()).start();
}
}
運行結果
三、管道
管道是基於“管道流”的通信⽅式,管道輸入/輸出流要用於線程之間的數據傳輸,而傳輸的媒介爲內存。
管道輸入/輸出流的體現:
基於字符的:PipedWriter 、 PipedReader 、
基於字節流的:PipedOutputStream 、 PipedInputStream
使⽤管道多半與I/O流相關。當我們⼀個線程需要先另⼀個線程發送⼀個信息(⽐如字符串)或者⽂件等等時,就需要使⽤管道通信了
像消息傳遞機制,通過管道,將一個線程中的消息發送給另一個
基於PipedWriter 和PipedReader 的實現的ReaderThread 和WriterThread 的通信
WriterThread 寫了內容,ReaderThread 讀到並打印
public class Piped {
public static void main(String[] args) throws IOException, InterruptedException {
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
writer.connect(reader);
new Thread(new ReaderThread(reader)).start();
Thread.sleep(10);
new Thread(new WriterThread(writer)).start();
}
static class ReaderThread implements Runnable{
private PipedReader in;
public ReaderThread(PipedReader in){
this.in=in;
}
@Override
public void run() {
System.out.println("this is a reader");
int receice=0;
try {
while ((receice=in.read())!=-1){
System.out.println("read "+(char) receice);
}
}catch (IOException ex){
ex.printStackTrace();
}
}
}
static class WriterThread implements Runnable{
private PipedWriter out;
public WriterThread(PipedWriter out){
this.out=out;
}
@Override
public void run() {
System.out.println("this is a writer");
try {
out.write("write A");
}catch (IOException ex){
ex.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
運行結果