經過不斷訪問共享對象的狀態,來判斷是否知足了線程執行要求java
定義操做類MyObject,內部有一個volatile關鍵字修飾的list成員變量,使用volatile主要是爲了讓操做類對象的改變可讓每一個線程都能感應到。代碼以下:ide
package com.feng.example; import java.util.ArrayList; import java.util.List; public class MyObject { //此處必須是volatile,不然線程B中的object感應不到線程A中的object的變化 volatile private List<String> list = new ArrayList<String>(); public void add() { list.add("hahaha"); } public int size(){ //System.out.println("hahah"); return list.size(); } }
定義兩個線程類MyThreadA 用於向MyObject對象中添加數據,MyThreadB用於在list中存在大於等於五個元素時退出函數
代碼以下:工具
package com.feng.example; public class MyThreadA extends Thread{ private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { try { for(int i=0; i<10; i++) { System.out.println("添加了第"+i+"個元素"); object.add(); Thread.sleep(1000); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.feng.example; public class MyThreadB extends Thread { private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { // object.print(); try { while (true) { if (object.size() >= 5) { System.out.println("有五個元素,線程B應該退出了"); // 終止線程 throw new InterruptedException(); } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
測試類代碼以下:測試
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadA(object); Thread b = new MyThreadB(object); a.start(); b.start(); } }
分析:線程b在while(true)無限循環中不斷的判斷object對象的個數,若是超過5就會終止線程。線程b須要不斷的去訪問object對象的size方法才能知道object對象的狀態。這就是輪詢的方式,很是的浪費cpu資源。
this
執行結果以下:spa
添加了第0個元素 添加了第1個元素 添加了第2個元素 添加了第3個元素 添加了第4個元素java.lang.InterruptedException 有五個元素,線程B應該退出了 at com.feng.example.MyThreadB.run(MyThreadB.java:20) 添加了第5個元素 添加了第6個元素 添加了第7個元素 添加了第8個元素 添加了第9個元素
使用wait和notify來實現多個線程間的通訊。下面介紹一下這兩個方法的注意事項:線程
(1)wait使線程進入阻塞隊列,notify喚醒保持同一鎖對象的wait阻塞線程code
(2)wait和notify都只能在synchronized語句塊或者synchronized方法中使用,保證使用時是持有鎖的,不然會拋出異常對象
(3)調用wait方法,直接釋放鎖,調用notify方法,執行完同步代碼段後才釋放鎖
(4)線程被notify喚醒後進入就緒隊列,從新競爭鎖,並非直接獲取鎖對象
(5)notify方法只喚醒一個線程,若是有多個wait線程,隨機喚醒一個,notifyAll是喚醒全部(前提仍是要有同一個鎖對象)
(6)notify方法執行以後,若是沒有wait阻塞線程,則直接忽略
(7)終止wait中的線程,拋出異常
(8)wait(long) 等待特定的時間,在特定時間內沒有被喚醒將自動喚醒
wait和notify,notifyAll方法是object中的方法,全部的類都是繼承於object,所以全部的類都有wait和notify,notifyAll方法。
使用等待/通知機制實現上述功能:
修改兩個線程類MyThreadA與MyThreadB,代碼以下:
package com.feng.example; public class MyThreadA extends Thread{ private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { try { synchronized(object) { for(int i=0; i<10; i++) { System.out.println("添加了第"+i+"個元素"); object.add(); if(object.size()==5) { object.notify(); System.out.println("通知已發出"); } Thread.sleep(1000); } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.feng.example; public class MyThreadB extends Thread { private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { // object.print(); try { synchronized(object) { if(object.size() < 5) { System.out.println("線程b等待"); object.wait(); System.out.println("線程b被喚醒 "); } throw new InterruptedException(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
測試類代碼修改以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadA(object); Thread b = new MyThreadB(object); b.start(); try { Thread.sleep(50); //確保線程b先執行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } a.start(); } }
分析:線程a,b啓動,線程a先獲取到object的鎖,往object對象中添加元素,添加到五個時,發出notify通知,可是線程a並不釋放鎖,而是繼續執行,執行完同步代碼段以後釋放鎖,而後線程b獲取到鎖,終止線程b
運行結果以下:
線程b等待 添加了第0個元素 添加了第1個元素 添加了第2個元素 添加了第3個元素 添加了第4個元素 通知已發出 添加了第5個元素 添加了第6個元素 添加了第7個元素 添加了第8個元素 添加了第9個元素 線程b被喚醒 java.lang.InterruptedException at com.feng.example.MyThreadB.run(MyThreadB.java:25)
上述程序須要保證是notify發生在wait以後,若是notify先發生,那麼wait將永遠等待
上述實驗也證實了執行notify後是不釋放鎖的,執行完同步代碼塊以後才能釋放鎖。
觀察下面代碼,下面代碼中的MyObject類爲上例中的MyObject類
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); try { object.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:object.wait()必需要有鎖對象纔可以調用,所以,這裏的調用是不對的,運行會拋出非法的監視器狀態違例
notify同理,這裏就不演示了。執行結果以下:
Exception in thread "main" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:485) at com.feng.example.ThreadTest.main(ThreadTest.java:13)
修改就是添加synchronized語句塊,修改以下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); try { synchronized(object) { object.wait(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
notify執行完同步代碼塊以後才能釋放鎖已經在2.1中證明了。下面驗證wait直接釋放鎖。
修改測試類代碼:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadB(object); Thread b = new MyThreadB(object); b.start(); a.start(); } }
運行結果以下:
線程b等待 線程b等待
能夠證明wait執行後直接釋放鎖
修改測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadB(object); a.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } a.interrupt(); } }
運行結果以下:
線程b等待 java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:485) at com.feng.example.MyThreadB.run(MyThreadB.java:22)
修改MyThreadB類:
package com.feng.example; public class MyThreadB extends Thread { private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { // object.print(); try { synchronized(object) { if(object.size() < 5) { System.out.println("線程b等待"+System.currentTimeMillis()); object.wait(1000); System.out.println("線程b被喚醒 "+System.currentTimeMillis()); } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
修改測試類代碼:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadB(object); a.start(); } }
運行結果以下:
線程b等待1449803493388 線程b被喚醒 1449803494402
此示例中並無notify方法喚醒線程a,是等待1s以後自動喚醒。
定義操做類:
package com.feng.example; import java.util.ArrayList; import java.util.List; public class MyObject { //此處必須是volatile,不然線程B中的object感應不到線程A中的object的變化 volatile private List<String> list = new ArrayList<String>(); public void add() { list.add("hahaha"); } public int size() { return list.size(); } public void sub(){ //System.out.println("hahah"); list.remove(0); } }
定義兩個線程類,線程B添加元素,線程A刪除元素
package com.feng.example; public class MyThreadA extends Thread { private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { try { synchronized (object) { if (object.size() <= 0) { System.out.println("等待===="+Thread.currentThread().getName()); object.wait(); System.out.println("被喚醒==="+Thread.currentThread().getName()); } object.sub(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.feng.example; public class MyThreadB extends Thread { private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { synchronized (object) { object.add(); System.out.println("添加一個元素"); object.notifyAll(); System.out.println("喚醒全部"); } } }
測試類代碼:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread b = new MyThreadB(object); Thread a = new MyThreadA(object); Thread c = new MyThreadA(object); a.start(); c.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } b.start(); } }
分析:a,c兩個線程都是執行的減操做,object中沒有數據,兩個線程都阻塞,線程b執行添加操做,喚醒a,c線程,只添加了一個元素,卻喚醒兩個元素去執行減操做,後減的線程會出現越界。
執行結果以下:
等待====Thread-1 等待====Thread-2 添加一個元素 Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.RangeCheck(ArrayList.java:547) at java.util.ArrayList.remove(ArrayList.java:387) at com.feng.example.MyObject.sub(MyObject.java:23) at com.feng.example.MyThreadA.run(MyThreadA.java:24) 喚醒全部 被喚醒===Thread-2 被喚醒===Thread-1
修改類MyThreadA的代碼,在喚醒後都要從新檢查一遍是否知足減的條件,將if修改成while
package com.feng.example; public class MyThreadA extends Thread { private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { try { synchronized (object) { while (object.size() <= 0) { System.out.println("等待===="+Thread.currentThread().getName()); object.wait(); System.out.println("被喚醒==="+Thread.currentThread().getName()); } object.sub(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
執行結果以下:
等待====Thread-1 等待====Thread-2 添加一個元素 喚醒全部 被喚醒===Thread-2 被喚醒===Thread-1 等待====Thread-1
主要用的輸入輸出流爲PipedInputStream PipedOutputStream
定義兩個操做類WriteData用於寫數據, ReaderData用於讀數據,代碼以下:
package com.feng.example; import java.io.IOException; import java.io.PipedOutputStream; public class WriteData { public void WriteMethod(PipedOutputStream out) { try { System.out.println("write:"); for(int i=0; i< 10; i++) { String data = ""+i; out.write(data.getBytes()); System.out.print(data); } System.out.println(); out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.feng.example; import java.io.IOException; import java.io.PipedInputStream; public class ReaderData { public void ReaderMethod(PipedInputStream in) { try { System.out.println("read:"); byte[] data = new byte[11]; int length = in.read(data); while (length != -1) { String newData = new String(data, 0, length); System.out.print(newData); length = in.read(data); } System.out.println(); in.close(); } catch (IOException e) { e.printStackTrace(); } } }
定義兩個線程類MyThreadA調用寫操做,MyThreadB調用讀操做,代碼以下:
package com.feng.example; import java.io.PipedOutputStream; public class MyThreadA extends Thread { private WriteData writeData; private PipedOutputStream out; public MyThreadA(WriteData writeData, PipedOutputStream out) { this.writeData = writeData; this.out = out; } @Override public void run() { writeData.WriteMethod(out); } }
package com.feng.example; import java.io.PipedInputStream; public class MyThreadB extends Thread { private ReaderData readerData; private PipedInputStream in; public MyThreadB(ReaderData readerData, PipedInputStream in) { this.readerData = readerData; this.in = in; } @Override public void run() { readerData.ReaderMethod(in); } }
測試類代碼以下:
package com.feng.example; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { WriteData wd = new WriteData(); ReaderData rd = new ReaderData(); PipedOutputStream out = new PipedOutputStream(); PipedInputStream in = new PipedInputStream(); out.connect(in); Thread a = new MyThreadA(wd, out); Thread b = new MyThreadB(rd, in); b.start(); Thread.sleep(2000); a.start(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:在測試類中定義管道輸入流in管道輸出流out,使用in.connect(out)或者out.connect(in)將輸入輸出流綁定。也就是輸入流讀取的是輸出流寫入的數據。程序首先執行線程b,線程b執行讀操做,此時輸出流並無寫任何數據,所以線程b在in.read(data)處阻塞。線程a執行寫操做後,線程b才獲取到數據繼續執行。
結果以下:
read: write: 0123456789 0123456789
主要就是將處理字節改成處理字符,將PipedInputStream改成PipedReader,將PipedOutputStream改成PipedWriter
修改後的操做類以下:
package com.feng.example; import java.io.IOException; import java.io.PipedWriter; public class WriteData { public void WriteMethod(PipedWriter out) { try { System.out.println("write:"); for(int i=0; i< 10; i++) { String data = ""+i; out.write(data); System.out.print(data); } System.out.println(); out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.feng.example; import java.io.IOException; import java.io.PipedReader; public class ReaderData { public void ReaderMethod(PipedReader in) { try { System.out.println("read:"); char[] data = new char[11]; int length = in.read(data); while (length != -1) { String newData = new String(data, 0, length); System.out.print(newData); length = in.read(data); } System.out.println(); in.close(); } catch (IOException e) { e.printStackTrace(); } } }
線程類以下:
package com.feng.example; import java.io.PipedWriter; public class MyThreadA extends Thread { private WriteData writeData; private PipedWriter out; public MyThreadA(WriteData writeData, PipedWriter out) { this.writeData = writeData; this.out = out; } @Override public void run() { writeData.WriteMethod(out); } }
package com.feng.example; import java.io.PipedReader; public class MyThreadB extends Thread { private ReaderData readerData; private PipedReader in; public MyThreadB(ReaderData readerData, PipedReader in) { this.readerData = readerData; this.in = in; } @Override public void run() { readerData.ReaderMethod(in); } }
測試類以下:
package com.feng.example; import java.io.IOException; import java.io.PipedReader; import java.io.PipedWriter; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { WriteData wd = new WriteData(); ReaderData rd = new ReaderData(); PipedWriter out = new PipedWriter(); PipedReader in = new PipedReader(); out.connect(in); Thread a = new MyThreadA(wd, out); Thread b = new MyThreadB(rd, in); b.start(); Thread.sleep(2000); a.start(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
運行結果:
read: write: 0123456789 0123456789
join方法用於等待線程對象銷燬,執行a.join()的線程阻塞,等待線程對象a執行完成後繼續執行。
定義線程類:
package com.feng.example; public class MyThread extends Thread { @Override public void run() { try { System.out.println(Thread.currentThread().getName()+"===="+System.currentTimeMillis()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"===="+System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
定義測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { Thread a = new MyThread(); a.start(); a.join(); Thread.sleep(100); System.out.println("線程a執行完了"+System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
運行結果:
Thread-0====1449821481676 Thread-0====1449821483689 線程a執行完了1449821483799
從結果能夠看出,main函數中最後的輸出語句是在線程a執行完成後輸出的。
在join過程當中,若是線程(執行join的線程好比上述的主線程)被中斷,拋出異常
join(long) 等待有限時間,注意與sleep(long)的區別
ThreadLocal主要解決線程間變量的隔離性,它會爲每個線程保存一份本身的值。在Struts中使用的就是ThreadLocal
查看下面代碼:
package com.feng.example; public class ThreadTest { /** * @param args */ private static ThreadLocal tl = new ThreadLocal(); public static void main(String[] args) { if(tl.get() == null) { System.out.println("沒有放過值"); tl.set("newData"); } System.out.println(tl.get()); System.out.println(tl.get()); } }
運行結果以下:
沒有放過值 newData newData
說明,沒有值時取出的爲null
下面來驗證ThreadLocal的隔離性,新建兩個線程,兩個線程向ThreadLocal中添加數據,觀察數據會不會出現混亂。
定義工具類:
package com.feng.example; public class Tools { public static ThreadLocal t = new ThreadLocal(); }
定義兩個線程類:
package com.feng.example; public class MyThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { Tools.t.set(Thread.currentThread().getName() +" "+ i); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"==="+Tools.t.get()); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package com.feng.example; public class MyThreadB extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { Tools.t.set(Thread.currentThread().getName() +" "+ i); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"==="+Tools.t.get()); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { Thread a = new MyThreadA(); Thread b = new MyThreadB(); a.start(); b.start(); } }
運行結果:
Thread-1===Thread-1 0 Thread-0===Thread-0 0 Thread-1===Thread-1 1 Thread-0===Thread-0 1 Thread-1===Thread-1 2 Thread-0===Thread-0 2 Thread-1===Thread-1 3 Thread-0===Thread-0 3 Thread-1===Thread-1 4 Thread-0===Thread-0 4 Thread-1===Thread-1 5 Thread-0===Thread-0 5 Thread-1===Thread-1 6 Thread-0===Thread-0 6 Thread-1===Thread-1 7 Thread-0===Thread-0 7 Thread-1===Thread-1 8 Thread-0===Thread-0 8 Thread-1===Thread-1 9 Thread-0===Thread-0 9
從結果中能夠看出,數據並無混亂,能夠得知t.get(),是根據執行這條語句的currentThread來提取的值,t.set()也是根絕currentThread來存的值,也就是說每個線程都會有一個數據的備份,都有本身的值,相互不會影響。
若是想修改初始值,能夠重寫ThreadLocal值的initialValue方法;
定義本身的類繼承ThreadLocal
package com.feng.example; public class MyThreadLocal extends ThreadLocal{ @Override protected Object initialValue() { // TODO Auto-generated method stub return "本身設置的初始值"; } }
測試:
package com.feng.example; public class ThreadTest { /** * @param args */ private static MyThreadLocal t = new MyThreadLocal(); public static void main(String[] args) { System.out.println(t.get()); } }
執行結果:
本身設置的初始值
可見本身設置的值生效了
此類主要能夠獲取到父線程的值,(父線程並非繼承關係的,是誰建立的誰就是父線程)
定義本身的ThreadLocal類繼承InheritableThreadLocal類,重寫initialValue方法,這裏返回時間,主要看何時初始化的。從而判斷是子線程初始化仍是父線程初始化的。
代碼以下:
package com.feng.example; import java.util.Date; public class MyThreadLocal extends InheritableThreadLocal{ @Override protected Object initialValue() { // TODO Auto-generated method stub return new Date(); } }
定義工具類:
package com.feng.example; public class Tools { public static MyThreadLocal t = new MyThreadLocal(); }
定義線程類:
package com.feng.example; public class MyThreadA extends Thread { @Override public void run() { System.out.println(Tools.t.get()); } }
定義測試類:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { System.out.println(Tools.t.get()); Thread a = new MyThreadA(); a.start(); } }
運行結果:
Fri Dec 11 16:49:48 CST 2015 Fri Dec 11 16:49:48 CST 2015
可見main線程中和a線程中的值是同樣的,時間同樣說明子線程繼承了main線程中的值。
若是想在繼承的時候修改,能夠重寫childValue方法
修改自定義ThreadLocal類,代碼以下:
package com.feng.example; import java.util.Date; public class MyThreadLocal extends InheritableThreadLocal{ @Override protected Object initialValue() { // TODO Auto-generated method stub return new Date(); } @Override protected Object childValue(Object parentValue) { // TODO Auto-generated method stub return parentValue +"==="+new Date(); } }
運行代碼以下:
Fri Dec 11 16:51:46 CST 2015 Fri Dec 11 16:51:46 CST 2015===Fri Dec 11 16:51:46 CST 2015
若是父線程中的值修改了,那麼子線程獲取的是在取值時候的值。