系列文章傳送門:java
Java多線程學習(二)synchronized關鍵字(1)程序員
Java多線程學習(二)synchronized關鍵字(2)github
Java多線程學習(四)等待/通知(wait/notify)機制編程
系列文章將被優先更新於微信公衆號「Java面試通關手冊」,歡迎廣大Java程序員和愛好技術的人員關注。網絡
本節思惟導圖: 多線程
思惟導圖源文件+思惟導圖軟件關注微信公衆號:「Java面試通關手冊」 回覆關鍵字:「Java多線程」 免費領取。併發
咱們經過以前幾章的學習已經知道在線程間通訊用到的synchronized關鍵字、volatile關鍵字以及等待/通知(wait/notify)機制。今天咱們就來說一下線程間通訊的其餘知識點:管道輸入/輸出流、Thread.join()的使用、ThreadLocal的使用。
管道輸入/輸出流和普通文件的輸入/輸出流或者網絡輸入、輸出流不一樣之處在於管道輸入/輸出流主要用於線程之間的數據傳輸,並且傳輸的媒介爲內存。
管道輸入/輸出流主要包括下列兩類的實現:
面向字節: PipedOutputStream、 PipedInputStream
面向字符: PipedWriter、 PipedReader
writeMethod方法
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
readMethod方法
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 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 e) {
e.printStackTrace();
}
}
複製代碼
咱們上面定義了兩個方法writeMethod和readMethod,前者用於寫字節/字符(取決於你用的是PipedOuputStream仍是PipedWriter),後者用於讀取字節/字符(取決於你用的是PipedInputStream仍是PipedReader).咱們定義了兩個線程threadRead和threadWrite ,threadRead線程運行readMethod方法,threadWrite運行writeMethod方法。而後 經過outputStream.connect(inputStream)或inputStream.connect(outputStream)使兩個管道流產生連接,這樣就能夠將數據進行輸入與輸出了。
運行結果:
在不少狀況下,主線程生成並起動了子線程,若是子線程裏要進行大量的耗時的運算,主線程每每將於子線程以前結束,可是若是主線程處理完其餘的事務後,須要用到子線程的處理結果,也就是主線程須要等待子線程執行完成以後再結束,這個時候就要用到join()方法了。另外,一個線程須要等待另外一個線程也須要用到join()方法。
Thread類除了提供join()方法以外,還提供了join(long millis)、join(long millis, int nanos)兩個具備超時特性的方法。這兩個超時方法表示,若是線程thread在指定的超時時間沒有終止,那麼將會從該超時方法中返回。
不使用join方法的弊端演示:
Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread threadTest = new MyThread();
threadTest.start();
//Thread.sleep(?);//由於不知道子線程要花的時間這裏不知道填多少時間
System.out.println("我想當threadTest對象執行完畢後我再執行");
}
static public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我想先執行");
}
}
}
複製代碼
運行結果:
使用join方法解決上面的問題:
Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread threadTest = new MyThread();
threadTest.start();
//Thread.sleep(?);//由於不知道子線程要花的時間這裏不知道填多少時間
threadTest.join();
System.out.println("我想當threadTest對象執行完畢後我再執行");
}
static public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我想先執行");
}
}
}
複製代碼
上面的代碼僅僅加上了一句:threadTest.join();。在這裏join方法的做用就是主線程須要等待子線程執行完成以後再結束。
join(long millis)中的參數就是設定的等待時間。
JoinLongTest.java
public class JoinLongTest {
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join(2000);// 只等2秒
//Thread.sleep(2000);
System.out.println(" end timer=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("begin Timer=" + System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製代碼
運行結果:
不論是運行threadTest.join(2000)仍是Thread.sleep(2000), 「end timer=1522036620288」語句的輸出都是間隔兩秒,「end timer=1522036620288」語句輸出後該程序還會運行一段時間,由於線程中的run方法中有Thread.sleep(10000)語句。
另外threadTest.join(2000) 和Thread.sleep(2000) 和區別在於: Thread.sleep(2000)不會釋放鎖,threadTest.join(2000)會釋放鎖 。
變量值的共享可使用public static變量的形式,全部線程都使用一個public static變量。 若是想實現每個線程都有本身的共享變量該如何解決呢? JDK中提供的ThreadLocal類正是爲了解決這樣的問題。 ThreadLocal類主要解決的就是讓每一個線程綁定本身的值,能夠將ThreadLocal類形象的比喻成存放數據的盒子,盒子中能夠存儲每一個線程的私有數據。
再舉個簡單的例子: 好比有兩我的去寶屋收集寶物,這兩個共用一個袋子的話確定會產生爭執,可是給他們兩我的每一個人分配一個袋子的話就不會出現這樣的問題。若是把這兩我的比做線程的話,那麼ThreadLocal就是用來這兩個線程競爭的。
ThreadLocal類相關方法:
方法名稱 | 描述 |
---|---|
get() | 返回當前線程的此線程局部變量的副本中的值。 |
set(T value) | 將當前線程的此線程局部變量的副本設置爲指定的值 |
remove() | 刪除此線程局部變量的當前線程的值。 |
initialValue() | 返回此線程局部變量的當前線程的「初始值」 |
Test1.java
public class Test1 {
public static ThreadLocal<String> t1 = new ThreadLocal<String>();
public static void main(String[] args) {
if (t1.get() == null) {
System.out.println("爲ThreadLocal類對象放入值:aaa");
t1.set("aaaֵ");
}
System.out.println(t1.get());//aaa
System.out.println(t1.get());//aaa
}
}
複製代碼
從運行結果能夠看出,第一次調用ThreadLocal對象的get()方法時返回的值是null,經過調用set()方法能夠爲ThreadLocal對象賦值。
若是想要解決get()方法null的問題,可使用ThreadLocal對象的initialValue方法。以下:
Test2.java
public class Test2 {
public static ThreadLocalExt t1 = new ThreadLocalExt();
public static void main(String[] args) {
if (t1.get() == null) {
System.out.println("從未放過值");
t1.set("個人值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
static public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return "我是默認值 第一次get再也不爲null";
}
}
}
複製代碼
Test3.java
/** *TODO 驗證線程變量間的隔離性 */
public class Test3 {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println(" 在Main線程中取值=" + Tools.tl.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static public class Tools {
public static ThreadLocalExt tl = new ThreadLocalExt();
}
static public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
static public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA線程中取值=" + Tools.tl.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
複製代碼
從運行結果能夠看出子線程和父線程各自擁有各自的值。
運行結果:
ThreadLocal類當然很好,可是子線程並不能取到父線程的ThreadLocal類的變量,InheritableThreadLocal類就是解決這個問題的。
取父線程的值:
修改Test3.java的內部類Tools 和ThreadLocalExt類以下:
static public class Tools {
public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt();
}
static public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
複製代碼
運行結果:
修改Test3.java的內部類Tools 和InheritableThreadLocalExt類以下:
static public class Tools {
public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt();
}
static public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
@Override
protected Object childValue(Object parentValue) {
return parentValue + " 我在子線程加的~!";
}
}
複製代碼
運行結果:
參考:
《Java多線程編程核心技術》
《Java併發編程的藝術》