Java高併發學習(一)
Java高併發學習(一)
初始執行緒:執行緒的基本操作
進行java併發設計的第一步,就是必須瞭解Java虛擬機器提供的執行緒操作的API。比如如何建立並啟動執行緒,如何終止執行緒,中斷執行緒等。
1.定義執行緒:
(1).繼承Thread方法,形式如下
public static class T1 extends Thread{
@Override
public void run(){
System.out.println("fist.T1.run()");
}
}
(2) .實現Runnable介面建立執行緒方式,形式如下
class MyThread implements Runnable{ @Override public void run(){ System.out.println("fist.T2.run()"); } } public class fist { public static void main(String args[]){ MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); t1.start(); } }
說明:Thread類有一個非常重要的構造方法public Thread(Runnable target),他傳入一個Runnable例項,在start()方法呼叫時,新的執行緒會執行Runnable.run()方法。
實際上,預設的Thread.run()就是這麼做的:
Public void run(){
if(target != null){
Target.run();
}
}
2.啟動執行緒:
啟動執行緒很簡單。只需要使用new關鍵字建立一個執行緒物件,並且將它start()起來即可。
Thread t1 = new Thread();
t1.start();
注意一下:
下面的程式碼能通過編譯,也能正常執行。但是,卻不能建立一個新的執行緒,而是在當前執行緒中呼叫
Thread t1 = new Thread();
t1.run();
因此希望大家特別注意,呼叫start()方法和run()方法的區別。
3.終止執行緒:
如何正常的關閉一個執行緒呢?查閱JDK,不難發現Thread提供了一個stop方法。如果使用stop()方法,就可以立即將一個執行緒終止,非常方便。但是eclipse會提示你stop()方法為廢棄方法。也就是說,在將來JDK會刪除該方法。
為什麼stop()方法會被廢棄呢?原因是stop()方法太過暴力,強行把在執行的執行緒停止,可能會導致資料不一致問題。
那如果需要停止一個執行緒時,應該怎麼做呢?其實方法很簡單,只是需要由我們自行決定執行緒何時退出就可以了。
例如:
class MyThread extends Thread{
volatile boolean stopme = false;
public void stopeMe(){
stopme = true;
}
@Override
public void run(){
while(true){
if(stopme){
System.out.println("exit by stop me");
break;
}
}
}
}
程式碼中定義了一個標記變數stopme,用於指示執行緒是否需要退出。當stopMe()方法被呼叫,stopme被設定為true,此時,執行緒會檢測到這個改動,執行緒就自然退出了。
4.執行緒中斷:
從表面上理解,中斷就是讓執行緒暫停執行的意思,實際上並非如此。在上一節中我們已經討論了stop()方法停止執行緒的害處,並且提供了一套完善執行緒退出功能。那在JDK中是否提供更強大的支援呢?答案是肯定的,那就是執行緒中斷。
與執行緒中斷有關的有三個方法,這三個方法看起來很像,所以可能會引起混淆和誤用,請大家注意。
public void Thread.interrupt() //中斷執行緒
Public void boolean Thread.isInterrupted() //判斷執行緒是否被中斷
Public static boolean Thread.interrupted() //判斷是否被中斷,並請除當前中斷狀態
Thread.interrupt()設定中斷標誌位,Thread.isInterrupted()檢查中斷標誌位,Thread.interrupted()清除中斷標誌位。
下面這段程式碼對t1進行了中斷,那麼t1中斷後,t1會停止執行嗎?
class MyThread extends Thread{
@Override
public void run(){
while(true){
System.out.println("Thread running");
}
}
}
public class fist{
public static void main(String args[]){
Thread t1 = new MyThread();
t1.start();
t1.interrupt();
}
}
在這裡雖然會t1進行了中斷,但是t1並沒處理中斷的邏輯,因此,即使t1執行緒被設定了中斷,但是這個中斷不會發生任何作用。
如果希望t1在中斷後退出,就必須為他增加相應的中斷處理程式碼:
class MyThread extends Thread{
@Override
public void run(){
while(true){
System.out.println("Thread running");
if(Thread.currentThread().isInterrupted()){
System.out.println("interrput");
break;
}
}
}
}
public class fist{
public static void main(String args[]) throws InterruptedException{
Thread t1 = new MyThread();
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}
特別注意,如果在迴圈體中出現了wait()或者sleep()這樣操作的時候,中斷可能會被忽略。
Thread.sleep()方法會讓當前執行緒休眠若干時間,他會丟擲一個interruptException中斷異常。interruptException不是執行時異常,也就是程式必須捕獲並處理他。當執行緒在休眠時,如果被中斷,這個異常就會產生。
class MyThread extends Thread{
@Override
public void run(){
while(true){
System.out.println("Thread running");
if(Thread.currentThread().isInterrupted()){
System.out.println("interrput");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("when sleep interrupt");
Thread.currentThread().interrupt();
}
System.out.println("Thread end");
}
}
}
public class fist{
public static void main(String args[]) throws InterruptedException{
Thread t1 = new MyThread();
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
如果執行緒執行到了sleep()程式碼段,主程式中斷執行緒,執行緒這這時候丟擲異常,進入catch的異常處理。在catch程式碼段中,由於捕獲到了中斷,我們可以立即退出執行緒。在這裡我們並沒有這麼做,因為也許在這段程式碼中,我們還必須進行後續處理,保證資料的一致性和完整性,因此,執行了Thread.interrupt()方法在次中斷自己,設定中斷標誌位。只有這麼做才能當執行緒休眠時響應中斷。
注意:Thread.sleep()方法由於中斷而丟擲異常,此時,它會清除中斷標誌位,如果不加處理,那麼在下一次迴圈開始前,就無法捕獲這個中斷,故在異常處理中,在次設定中斷標記位。
5.等待(wait)和通知(notify)
為了支援多執行緒之間的協作,JDK提供了兩個非常重要的介面,執行緒等待wait()方法和執行緒通知方法notify()。這兩個方法不是在Thread類中的,而是Object類的。這也意味著任何物件都能呼叫者這兩種方法。
public final void wait() throws InterruptException
public final native void notify()
那wait()和notify()是怎麼工作的呢?如果一個執行緒呼叫了object.wait()方法,那麼他就會進入object物件的等待佇列。這個佇列中可能會有多個等待執行緒,因為系統運行同時等待某個物件。當object.notify()被呼叫時,他就會從這個等待佇列中隨機選擇一個執行緒喚醒。這裡希望大家注意,這個喚醒過程是不公平的,並不是先等待的執行緒會優先選擇。
除了object.notify()之外還有object.notifyAll()方法,他會喚醒等待佇列中的所有執行緒。
這裡還需要注意一點,object.wait()方法和object.notify()方法並不是可以隨便呼叫的。他必須包含在對應的synchronized語句中,無論是wait還是notify都需要首先獲得目標物件的一個監視器。
這裡給出簡單實用wait和notify的案例:
import java.util.Objects;
public class fist{
final static Object object = new Object();
public static class MyThread_1 extends Thread{
@Override
public void run(){
synchronized (object) {
System.out.println(System.currentTimeMillis()+"T1 start");
try {
System.out.println(System.currentTimeMillis()+"T1 wait");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+"T1 end");
}
}
}
public static class MyThread_2 extends Thread{
@Override
public void run(){
synchronized (object) {
System.out.println(System.currentTimeMillis()+"T2 start and notify");
object.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]){
Thread t1 = new MyThread_1();
Thread t2 = new MyThread_2();
t1.start();
t2.start();
}
}
上述程式碼中,開啟了兩個執行緒t1和t2。t1執行了object.wait()方法。注意,在wait方法執行前,t1申請了object物件鎖。因此在執行object.wait()時,他說持有鎖的。Wait()方法執行後,t1會進行等待,並且釋放object物件的鎖。t2在執行notify之前也會獲得object物件鎖,在notify執行後釋放object物件的鎖。
輸出結果如下:
6.掛起(suspend)和繼續執行(resume):
這兩個操作是一對相反的操作,被掛起的執行緒必須等到resume()操作後才能繼續執行。但如果仔細閱讀文件後會發現,他們早已被標註為廢棄方法,不推薦使用。
不推薦使用suspend()去掛起執行緒的原因,是因為suspend()在導致執行緒暫停的同時,並不會去釋放任何資源鎖。此時,任何執行緒想訪問它佔用的鎖時,都會被牽連,無法正常執行。
如果,resume操作意外的再suspend前就執行了,那麼被掛起的執行緒就很難有機會繼續執行了。並且更嚴重的是他的鎖並不會被釋放,因此可能會導致整個系統無法正常工作。而且,對於被掛起的執行緒,從執行緒狀態上看,居然還是runnable,這也會嚴重的影響到我們對系統當前狀態的判斷。
import java.util.Objects;
public class fist{
final static Object object = new Object();
public static class MyThread extends Thread{
public MyThread(String name){
super.setName(name);
}
@Override
public void run(){
synchronized (object) {
System.out.println("in "+getName());
Thread.currentThread().suspend();
}
System.out.println("end "+getName());
}
}
public static void main(String args[]) throws InterruptedException{
Thread t1 = new MyThread("t1");
Thread t2 = new MyThread("t2");
t1.start();
Thread.sleep(2000);
t2.start();
t1.resume();
t2.resume();
}
}
輸出:
這段程式碼的程式不會退出。而是會掛起。使用jstack命令列印系統執行緒資訊可以看到。
這時我們需要注意,當前系統中,t2其實是被掛起的。但是他的執行緒狀態是runnable,這很有可能對導致我們誤判當前系統的狀態。同時,雖然主程式已經呼叫了resume(),但是由於時間先後順序的緣故,resume並沒有生效!這就導致了執行緒t2被永久掛起,並且永久佔用了物件object的鎖。這對於系統來說極有可能是致命的。
沒有輸出”end t2”的原因是t2.resume()在t2.suspend()前就運行了,導致t2永遠被掛起,如果把程式碼改寫成如下,才會輸出”end t2”。
import java.util.Objects;
public class fist{
final static Object object = new Object();
public static class MyThread extends Thread{
public MyThread(String name){
super.setName(name);
}
@Override
public void run(){
synchronized (object) {
System.out.println("in "+getName());
Thread.currentThread().suspend();
}
System.out.println("end "+getName());
}
}
public static void main(String args[]) throws InterruptedException{
Thread t1 = new MyThread("t1");
Thread t2 = new MyThread("t2");
t1.start();
Thread.sleep(2000); //設定延遲
t2.start();
t1.resume();
t2.resume();
}
}
輸出:
7.等待執行緒結束(join)和謙讓(yield):
很多時候,一個執行緒的輸入可能會非常依賴另一個或多個執行緒的輸出,此時,這個執行緒就需要等待依賴執行緒的執行完畢,才能繼續執行。JDK提供了join()操作來實現這個功能,如下所示,顯示了兩個join()方法:
public final void join() throws InterruptedException
public final synchronized void join() throws InterruptedException
第一個join方法表示無限等待,他會一直阻塞執行緒,直到目標執行緒執行完畢。第二個給出了一個最大等待時間,如果超過給定時間目標執行緒還在執行,當前執行緒也會“等不及了”,而繼續往下執行。
這裡提供一個簡單的join例項,以供參考:
import java.util.Objects;
public class fist{
public volatile static int i = 0;
public static class MyThread extends Thread{
@Override
public void run(){
for(i=0;i<10000000;i++);
}
}
public static void main(String args[]) throws InterruptedException{
Thread t = new MyThread();
t.start();
t.join();
System.out.println(i);
}
}
主函式中,如果不使用join等待MyThread執行緒,那麼得到的i可能是0或者是一個很小的數值。因為MyThread還沒開始執行,i的值就被打印出來了。但在使用join方法後,表示主執行緒願意等待MytThread執行緒執行完畢,跟著MyThread一起往前走,故在join返回時,MyThread已經執行完畢,故i總是10000000。
另外一個比較有趣的方法,Thread.yield()。
這是一個靜態方法,一旦執行,它會使當前執行緒讓出cpu。但要注意讓出cpu不代表不在執行了。當前執行緒在讓出cpu後,還會進行cpu的資源爭奪,但是能否被在次分配到,就不一定了。
如果你覺得一個執行緒不那重要,或者優先順序非常低,而且又害怕它會佔用太多的cpu資源,那麼可以在適當的時候呼叫yield,給予其他重要執行緒更多工作機會。