java 线程不足_Java 线程基础知识
wait() 和notify()、notifyAll()
這三個方法用于協調多個線程對共享數據的存取,所以必須在 Synchronized 語句塊內使用這三個方法,否則會拋出錯 IllegalMonitorStateException。前面說過 Synchronized 這個關鍵字用于保護共享數據,阻止其他線程對共享數據的存取。但是這樣程序的流程就很不靈活了,如何才能在當前線程還沒退出 Synchronized 數據塊時讓其他線程也有機會訪問共享數據呢?此時就用這三個方法來靈活控制。
wait() 方法使當前線程被阻塞掛起暫停執行并釋放對象鎖標志,讓其他線程可以進入 Synchronized 數據塊,當前線程被放入對象等待池中。當調用共享對象的 notify() 或者 notifyAll() 方法才會返回,此時返回的線程會加入到鎖標志等待池中,只有鎖標志等待池中的線程能夠獲取鎖標志,如果鎖標志等待池中沒有線程,則 notify() 不起作用。
notifyAll() 則從對象等待池中移走所有等待那個對象的線程并放到鎖標志等待池中。
需要注意的是,當線程調用共享對象的 wait() 方法時,當前線程只會釋放當前共享對象的鎖,當前線程持有的其他共享對象的監視器鎖并不會釋放。
線程中斷
為啥需要中斷呢?下面簡單的舉例情況:
比如我們會啟動多個線程做同一件事,比如搶 12306 的火車票,我們可能開啟多個線程從多個渠道買火車票,只要有一個渠道買到了,我們會通知取消其他渠道。這個時候需要關閉其他線程;
很多線程的運行模式是死循環,比如在生產者/消費者模式中,消費者主體就是一個死循環,它不停的從隊列中接受任務,執行任務,在停止程序時,我們需要一種”優雅”的方法以關閉該線程;
在一些場景中,比如從第三方服務器查詢一個結果,我們希望在限定的時間內得到結果,如果得不到,我們會希望取消該任務;
上面這幾個例子線程已經在運行了,并不好去干涉,但是可以通過中斷,告訴這個線程,你應該中斷了。比如上面的例子中的線程再收到中斷后,可以通過中斷標志來結束線程的運行。當然,你也可以收到后,不做任何處理,這也是可以的。
在 Java 中,停止一個線程的主要機制是中斷,中斷并不是強迫終止一個線程,它是一種協作機制,是給線程傳遞一個取消信號,但是由線程來決定如何以及何時退出。
需要注意的是:在停止線程的時候,不要調用 stop 方法,該方法已經被廢棄了,并且會帶來不可預測的影響。
線程對中斷的反應
RUNNABLE:線程在運行或具備運行條件只是在等待操作系統調度
WAITING/TIMED_WAITING:線程在等待某個條件或超時
BLOCKED:線程在等待鎖,試圖進入同步塊
NEW/TERMINATED:線程還未啟動或已結束
線程中斷常用的方法
interrupt() :中斷線程,將會設置該線程的中斷狀態位,即設置為true,中斷的結果線程是死亡、還是等待新的任務或是繼續運行至下一步,就取決于這個程序本身。線程會不時地檢測這個中斷標示位,以判斷線程是否應該被中斷(中斷標示值是否為true)。它并不像stop方法那樣會中斷一個正在運行的線程。
interrupted():第一次使用返回true,并清除中斷標志位,在此之后查詢中斷狀態isInterrupt()都會返回false,剛剛第一個例子也看到了,利用? ? 第一次返回的true可以跳出循環。第二次以及以后都是返回false。
isInterrupted():僅僅查詢中斷標志位來判斷是否發生中斷并返回true或者false。
RUNNABLE 狀態
如果線程在運行中,interrupt() 只是會設置線程的中斷標志位,沒有任何其它作用。線程應該在運行過程中合適的位置檢查中斷標志位,比如說,如果主體代碼是一個循環,可以在循環開始處進行檢查,如下所示:
public class InterruptRunnableDemo extendsThread {
@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) { // 也可以使用 !Thread.currentThread().interrupted() 來判斷有沒有中斷//... 單次循環代碼
}
System.out.println("done ");
}public static void main(String[] args) throwsInterruptedException {
Thread thread= newInterruptRunnableDemo();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
WAITING/TIMED_WAITING
線程執行如下方法會進入WAITING狀態:
public final void join() throwsInterruptedExceptionpublic final void wait() throws InterruptedException
執行如下方法會進入TIMED_WAITING狀態:
public final native void wait(long timeout) throwsInterruptedException;public static native void sleep(long millis) throwsInterruptedException;public final synchronized void join(long millis) throws InterruptedException
在這些狀態時,對線程對象調用 interrupt() 會使得該線程拋出 InterruptedException,需要注意的是,拋出異常后,中斷標志位會被清空(線程的中斷標志位會由 true 重置為false,因為線程為了處理異常已經重新處于就緒狀態),而不是被設置。比如說,執行如下代碼:
Thread t = newThread (){
@Overridepublic voidrun() {try{
Thread.sleep(1000);
}catch(InterruptedException e) {//exception被捕獲,但是為輸出為false 因為標志位會被清空
System.out.println(isInterrupted());
}
}
};
t.start();try{
Thread.sleep(100);
}catch(InterruptedException e) {
}
t.interrupt();//置為true
InterruptedException 是一個受檢異常,線程必須進行處理。我們在異常處理中介紹過,處理異常的基本思路是,如果你知道怎么處理,就進行處理,如果不知道,就應該向上傳遞,通常情況下,你不應該做的是,捕獲異常然后忽略。
捕獲到 InterruptedException,通常表示希望結束該線程,線程大概有兩種處理方式:
向上傳遞該異常,這使得該方法也變成了一個可中斷的方法,需要調用者進行處理
有些情況,不能向上傳遞異常,比如Thread的run方法,它的聲明是固定的,不能拋出任何受檢異常,這時,應該捕獲異常,進行合適的清理操作,清理后,一般應該調用Thread的interrupt方法設置中斷標志位,使得其他代碼有辦法知道它發生了中斷
第一種方式的示例代碼如下:
//拋出中斷異常,由調用者捕獲
public void interruptibleMethod() throwsInterruptedException{//... 包含wait, join 或 sleep 方法
Thread.sleep(1000);
}
第二種方式的示例代碼如下:
public class InterruptWaitingDemo extendsThread {
@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) {try{//模擬任務代碼
Thread.sleep(2000);
}catch(InterruptedException e) {//... 清理操作
System.out.println(isInterrupted());//false//重設中斷標志位為true
Thread.currentThread().interrupt();
}
}
System.out.println(isInterrupted());//true
}public static voidmain(String[] args) {
InterruptWaitingDemo thread= newInterruptWaitingDemo();
thread.start();try{
Thread.sleep(100);
}catch(InterruptedException e) {
}
thread.interrupt();
}
}
BLOCKED
如果線程在等待鎖,對線程對象調用interrupt()只是會設置線程的中斷標志位,線程依然會處于BLOCKED狀態,也就是說,interrupt()并不能使一個在等待鎖的線程真正”中斷”。我們看段代碼:
public class InterruptWaitingDemo extendsThread {
@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) {try{//模擬任務代碼
Thread.sleep(2000);
}catch(InterruptedException e) {//... 清理操作//重設中斷標志位
Thread.currentThread().interrupt();
}
}
System.out.println(isInterrupted());
}public static voidmain(String[] args) {
InterruptWaitingDemo thread= newInterruptWaitingDemo();
thread.start();try{
Thread.sleep(100);
}catch(InterruptedException e) {
}
thread.interrupt();
}
}
BLOCKED 如果線程在等待鎖,對線程對象調用 interrupt() 只是會設置線程的中斷標志位,線程依然會處于 BLOCKED 狀態,也就是說,interrupt() 并不能使一個在等待鎖的線程真正”中斷”。我們看段代碼:
public classInterruptSynchronizedDemo {private static Object lock = new Object();//monitor
private static class A extendsThread {
@Overridepublic voidrun() {//等待lock鎖
synchronized(lock) {//等待標志位被置為true
while (!Thread.currentThread().isInterrupted()) {
}
}
System.out.println("exit");
}
}public static void test() throwsInterruptedException {synchronized (lock) {//獲取鎖
A a = newA();
a.start();
Thread.sleep(1000);//a在等待lock鎖,interrupt 無法中斷
a.interrupt();//a線程加入當前線程,等待執行完畢
a.join();
}
}public static void main(String[] args) throwsInterruptedException {
test();
}
}
test 方法在持有鎖 lock 的情況下啟動線程 a,而線程 a 也去嘗試獲得鎖 lock,所以會進入鎖等待隊列,隨后 test 調用線程 a 的 interrupt 方法并等待線程線程 a 結束,線程 a 會結束嗎?不會,interrupt 方法只會設置線程的中斷標志,而并不會使它從鎖等待隊列中出來。線程a 會一直嘗試獲取鎖,但是主線程也在等待 a 結束才會釋放鎖,所以相互之間互為等待,不能結束。
我們稍微修改下代碼,去掉 test方法中的最后一行 a.join(),即變為:
public static void test() throwsInterruptedException {synchronized(lock) {
A a= newA();
a.start();
Thread.sleep(1000);
a.interrupt();
}//lock鎖釋放后 A線程重隊列中出來
}
這時,程序就會退出。為什么呢?因為主線程不再等待線程 a 結束,釋放鎖 lock 后,線程 a 會獲得鎖,然后檢測到發生了中斷,所以會退出。
在使用 synchronized 關鍵字獲取鎖的過程中不響應中斷請求,這是 synchronized 的局限性。如果這對程序是一個問題,應該使用顯式鎖,java 中的 Lock 接口,它支持以響應中斷的方式獲取鎖。對于 Lock.lock(),可以改用 Lock.lockInterruptibly(),可被中斷的加鎖操作,它可以拋出中斷異常。等同于等待時間無限長的 Lock.tryLock(long time, TimeUnit unit)。
NEW/TERMINATE
如果線程尚未啟動 (NEW),或者已經結束 (TERMINATED),則調用 interrupt() 對它沒有任何效果,中斷標志位也不會被設置。比如說,以下代碼的輸出都是 false。
public classInterruptNotAliveDemo {private static class A extendsThread {
@Overridepublic voidrun() {
}
}public static void test() throwsInterruptedException {
A a= newA();
a.interrupt();
System.out.println(a.isInterrupted());
a.start();
Thread.sleep(100);
a.interrupt();
System.out.println(a.isInterrupted());
}public static void main(String[] args) throwsInterruptedException {
test();
}
}
IO操作
如果線程在等待 IO 操作,尤其是網絡 IO,則會有一些特殊的處理,我們沒有介紹過網絡,這里只是簡單介紹下。
實現此 InterruptibleChannel 接口的通道是可中斷的:如果某個線程在可中斷通道上因調用某個阻塞的 I/O 操作(常見的操作一般有這些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而進入阻塞狀態,而另一個線程又調用了該阻塞線程的 interrupt 方法,這將導致該通道被關閉,并且已阻塞線程接將會收到 ClosedByInterruptException,并且設置已阻塞線程的中斷狀態。另外,如果已設置某個線程的中斷狀態并且它在通道上調用某個阻塞的 I/O 操作,則該通道將關閉并且該線程立即接收到 ClosedByInterruptException;并仍然設置其中斷狀態。
如果線程阻塞于 Selector 調用,則線程的中斷標志位會被設置,同時,阻塞的調用會立即返回。
我們重點介紹另一種情況,InputStream 的 read 調用,該操作是不可中斷的,如果流中沒有數據,read 會阻塞 (但線程狀態依然是 RUNNABLE ),且不響應 interrupt(),與 synchronized 類似,調用 interrupt() 只會設置線程的中斷標志,而不會真正”中斷”它,我們看段代碼
public classInterruptReadDemo {private static class A extendsThread {
@Overridepublic voidrun() {while(!Thread.currentThread().isInterrupted()){try{
System.out.println(System.in.read())//wait input
} catch(IOException e) {
e.printStackTrace();
}
}
System.out.println("exit");
}
}public static void main(String[] args) throwsInterruptedException {
A t= newA();
t.start();
Thread.sleep(100);
t.interrupt();
}
}
線程t啟動后調用 System.in.read() 從標準輸入讀入一個字符,不要輸入任何字符,我們會看到,調用 interrupt() 不會中斷 read(),線程會一直運行。
不過,有一個辦法可以中斷 read() 調用,那就是調用流的 close 方法,我們將代碼改為:
public classInterruptReadDemo {private static class A extendsThread {
@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) {try{
System.out.println(System.in.read());
}catch(IOException e) {
e.printStackTrace();
}
}
System.out.println("exit");
}public voidcancel() {try{
System.in.close();
}catch(IOException e) {
}
interrupt();
}
}public static void main(String[] args) throwsInterruptedException {
A t= newA();
t.start();
Thread.sleep(100);
t.cancel();
}
}
我們給線程定義了一個 cancel 方法,在該方法中,調用了流的 close 方法,同時調用了 interrupt 方法,這次,程序會輸出:
-1exit
也就是說,調用close方法后,read方法會返回,返回值為-1,表示流結束。
如何正確地取消/關閉線程
1. 以上,我們可以看出,interrupt 方法不一定會真正”中斷”線程,它只是一種協作機制,如果 不明白線程在做什么,不應該貿然的調用線程的 interrupt 方法,以為這樣就能取消線程。
2. 對于以線程提供服務的程序模塊而言,它應該封裝取消/關閉操作,提供單獨的取消/關閉方法給調用者,類似于 InterruptReadDemo 中演示的 cancel 方法,外部調用者應該調用這些方法而不是直接調用 interrupt。
3. Java并發庫的一些代碼就提供了單獨的取消/關閉方法,比如說,Future接口提供了如下方法以取消任務:boolean cancel(boolean mayInterruptIfRunning);
4. 再比如,ExecutorService提供了如下兩個關閉方法:
voidshutdown();
List shutdownNow();
5. Future 和 ExecutorService 的 API 文檔對這些方法都進行了詳細說明,這是我們應該學習的方式。
參考文章:
總結
以上是生活随笔為你收集整理的java 线程不足_Java 线程基础知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 斗地主发牌编程PHP,php模拟实现斗地
- 下一篇: Super Enhancer(超级增强子