深入理解Java多執行緒(三)
關於java多執行緒的概念以及基本用法:java多執行緒基礎
3, 執行緒間通訊
執行緒在作業系統中是獨立的個體,經過特殊的處理,執行緒間可以實現通訊,進而成為一個整體,提高CPU利用率
3.1,等待/通知機制
等待:wait()方法作用是使當前執行程式碼的執行緒阻塞,在呼叫wait()之前,執行緒必須獲得該物件的物件級別鎖,即只能在同步方法或同步塊中呼叫wait()方法。執行wait()方法後,當前執行緒會釋放鎖。如果呼叫wait()方法時沒有鎖,那麼會丟擲IllegalMonitorStateException
通知:notify()也要在同步方法或者同步程式碼塊中呼叫,呼叫之前,執行緒也必須獲得該物件的物件級別的鎖,若沒有,則會丟擲IllegalMonitorStateException。該方法用於通知那些可能等待該物件的物件鎖的其他執行緒,如果有多個執行緒等待,則由執行緒規劃器隨機挑選其中一個呈wait狀態的執行緒,對其發出通知,並使它等待獲取該物件的物件鎖。執行notify()方法後,當前執行緒不會馬上釋放該物件鎖,呈wait狀態的執行緒也不能馬上獲取該物件鎖,要等執行notify()方法的執行緒執行完,當前執行緒才會釋放鎖。
方法notifyAll()可以喚醒全部執行緒
新建MyThread1類:
public class MyThread1 extends Thread{
private Object lock;
public MyThread1(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized(lock) {
System.out.println("開始 wait time" +System.currentTimeMillis());
lock.wait();
System.out.println("結束 wait time"+System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
新建MyThread2類:
public class MyThread2 extends Thread{
private Object lock;
public MyThread2(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
synchronized(lock) {
System.out.println("開始 notify time"+System.currentTimeMillis());
lock.notify();
System.out.println("結束 notify time"+System.currentTimeMillis());
}
}
}
測試類:
public class Test {
public static void main(String[] args) {
try {
Object lock = new Object();
MyThread1 t1 = new MyThread1(lock);
t1.start();
Thread.sleep(3000);
MyThread2 t2 = new MyThread2(lock);
t2.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
結果:
開始 wait time1534469153084
開始 notify time1534469156084
結束 notify time1534469156084
結束 wait time1534469156084
執行緒t1在執行wait()方法後進入阻塞狀態,3秒後執行緒t2執行,執行了notify()方法後執行緒t2在執行完程式碼塊後喚醒執行緒t1
方法wait(time)的作用是等待一段時間是否有執行緒對鎖進行喚醒,如果超過這個時間就自動喚醒
public class MyRunnable {
static private Object lock = new Object();
static private Runnable runnable1 = new Runnable() {
@Override
public void run() {
try {
synchronized(lock) {
System.out.println("wait begin timer="+System.currentTimeMillis());
lock.wait(5000);
System.out.println("wait end timer="+System.currentTimeMillis());
}
}catch (Exception e) {
e.printStackTrace();
}
}
};
public static void main(String[] args) {
Thread t= new Thread(runnable1);
t.start();
}
}
結果:
wait begin timer=1534473901885
wait end timer=1534473906886
3.2,通過管道進行執行緒間通訊:位元組流
Java語言裡提供了很多的輸入/輸出流Stream,便於資料讀寫,pipeStream(管道流)是一種特殊的流,用於在不同執行緒間直接傳送資料,一個執行緒傳送資料到輸出管道,另一個執行緒從輸入管道中讀取資料,可以實現執行緒間的通訊
Java的JDK提供了4個類
- 位元組流:PipedInputStream和PipedOutPutStream
- 字元流:PipedReader和PipedWriter
先看看位元組流:
新建WriteData類:
public class WriteData {
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write: ");
for(int i=0;i<20;i++) {
String outData = ""+(i+1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
新建ReadData類:
public class ReadData {
public void readMethod(PipedInputStream input) {
try {
System.out.println("read: ");
byte[] byteArray = new byte[20];
int readLength = input.read(byteArray);
while (readLength!=-1) {
String newData = new String(byteArray,0,readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
新建兩個執行緒:
public class ThreadWrite extends Thread{
private WriteData write;
private PipedOutputStream out;
public ThreadWrite(WriteData write, PipedOutputStream out) {
super();
this.write = write;
this.out = out;
}
@Override
public void run() {
write.writeMethod(out);
}
}
public class ThreadRead extends Thread{
private ReadData read;
private PipedInputStream input;
public ThreadRead(ReadData read, PipedInputStream input) {
super();
this.read = read;
this.input = input;
}
@Override
public void run() {
read.readMethod(input);
}
}
測試類:
public class Run {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedInputStream inputStream =new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
//inputStream.connect(outputStream);
outputStream.connect(inputStream);
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
Thread.sleep(2000);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
}catch(InterruptedException e1) {
e1.printStackTrace();
}
}
}
結果:
read:
write:
1234567891011121314151617181920
1234567891011121314151617181920
再看看字元流:
WriteData類:
public class WriteData {
public void writeMethod(PipedWriter out) {
try {
System.out.println("write: ");
for(int i=0;i<20;i++) {
String outData = ""+(i+1);
out.write(outData);
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ReadData類:
public class ReadData {
public void readMethod(PipedReader input) {
try {
System.out.println("read: ");
char[] byteArray = new char[20];
int readLength = input.read(byteArray);
while (readLength!=-1) {
String newData = new String(byteArray,0,readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
兩個執行緒類:
public class ThreadRead extends Thread{
private ReadData read;
private PipedReader input;
public ThreadRead(ReadData read, PipedReader input) {
super();
this.read = read;
this.input = input;
}
@Override
public void run() {
read.readMethod(input);
}
}
public class ThreadWrite extends Thread{
private WriteData write;
private PipedWriter out;
public ThreadWrite(WriteData write, PipedWriter out) {
super();
this.write = write;
this.out = out;
}
@Override
public void run() {
write.writeMethod(out);
}
}
測試類:
public class Run {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedReader inputStream =new PipedReader();
PipedWriter outputStream = new PipedWriter();
//inputStream.connect(outputStream);
outputStream.connect(inputStream);
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
Thread.sleep(2000);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
}catch(InterruptedException e1) {
e1.printStackTrace();
}
}
}
結果:
read:
write:
1234567891011121314151617181920
1234567891011121314151617181920
3.2,方法join的使用
很多時候,主執行緒建立並啟動子執行緒,子執行緒要進行一些耗時的計算,主執行緒往往早於子執行緒結束,如果主執行緒想等待子執行緒結束,則需要join()方法
新建MyThread類:
public class MyThread extends Thread{
@Override
public void run() {
try {
int secondValue = (int)(Math.random()*1000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
測試類:
public class Test {
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join();
System.out.println("我要等子執行緒結束再開始");
} catch (Exception e) {
e.printStackTrace();
}
}
}
結果:
231
我要等子執行緒結束再開始
join()方法的作用是使所屬執行緒物件正常執行run()方法,使當前主執行緒處於阻塞狀態,等子執行緒結束後再執行主執行緒,join在內部使用wait()方法進行等待
方法join(time)和sleep(time)的區別?
這兩個方法都會等待time時間再執行,但是在同步的處理上是不一樣的
join(time)的功能是在內部使用wait(time)方法,所以join方法其實會釋放鎖,
join原始碼:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
在執行join(time)方法後,當前執行緒的鎖被釋放,此時其他執行緒可以呼叫此執行緒的同步方法,而sleep(time)方法則不會釋放鎖
3.3,類ThreadLocal的使用
ThreadLocal為變數在每個執行緒中都建立了一個副本,所以每個執行緒都可以訪問自己內部的副本變數
新建Tools類:
public class Tools {
public static ThreadLocal t1 = new ThreadLocal();
}
兩個執行緒類:
public class ThreadA extends Thread{
@Override
public void run() {
try {
for(int i=0;i<10;i++) {
Tools.t1.set("ThreadA"+(i+1));
System.out.println("ThreadA get Value="+Tools.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadB extends Thread{
@Override
public void run() {
try {
for(int i=0;i<10;i++) {
Tools.t1.set("ThreadB"+(i+1));
System.out.println("ThreadB get Value="+Tools.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
測試類:
public class Run {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for(int i=0;i<10;i++) {
Tools.t1.set("Main"+(i+1));
System.out.println("Main get Value="+Tools.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
Main get Value=Main1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
ThreadB get Value=ThreadB2
ThreadA get Value=ThreadA2
Main get Value=Main2
Main get Value=Main3
ThreadB get Value=ThreadB3
ThreadA get Value=ThreadA3
Main get Value=Main4
ThreadB get Value=ThreadB4
ThreadA get Value=ThreadA4
ThreadB get Value=ThreadB5
ThreadA get Value=ThreadA5
Main get Value=Main5
Main get Value=Main6
ThreadA get Value=ThreadA6
ThreadB get Value=ThreadB6
ThreadB get Value=ThreadB7
ThreadA get Value=ThreadA7
Main get Value=Main7
ThreadB get Value=ThreadB8
Main get Value=Main8
ThreadA get Value=ThreadA8
Main get Value=Main9
ThreadA get Value=ThreadA9
ThreadB get Value=ThreadB9
Main get Value=Main10
ThreadB get Value=ThreadB10
ThreadA get Value=ThreadA10
由結果可以看到有雖然有3個執行緒set()資料值但是每個執行緒都能取出屬於自己的資料
3.4,類InheritableThreadLocal的使用
使用InheritableThreadLocal類可以在子執行緒中取得父執行緒繼承下來的值
如果在子執行緒取得值的同時,主執行緒將InheritableThreadLocal中的值進行更改,那麼子執行緒取到的值還是舊值