天天看點

Java并行程式基礎

thread t1 = new thread(()->{

int i = 1;

while (true) {

if (thread.currentthread().isinterrupted()) {

system.out.println("interrupt!");

break;

}

system.out.println("i'm working " + i++);

thread.yield();

});

t1.start();

thread.sleep(2000);

t1.interrupt();

這看起來跟前面增加标志位的手法非常相似,但是中斷的功能更為強勁。比如,如果在循環體中,出現了類似于wait()或者sleep()這樣的操作,則隻能通過中斷來識别了。

線程的睡眠sleep

==========

public static native void sleep(long millis) throws interruptedexception;

?該方法會讓目前線程休眠若幹時間,會抛出interruptedexception中斷異常。interruptedexception不是運作時異常,也就是程式必須捕獲并且處理它,當線程在sleep休眠時,如果被中斷,這個異常就會發生。

public static void main(string[] args) throws interruptedexception {

try {

} catch (interruptedexception e) {

system.out.println("interrupted when sleep");

thread.currentthread().interrupt();

thread.sleep(1000);

如果在sleep的時候,線程被中斷,則程式會抛出異常,并進入異常處理。在catch字句裡,由于已經捕獲了中斷,我們可以立即退出線程,但是并沒有這麼做。因為也許在這段代碼中,還必須進行後續的處理,保障資料的一緻性和完整性。是以,執行了interrupt()方法再次中斷自己,置上中斷标志位。隻有這麼做,在檢查isinterrupted(),才能發現目前線程已經被中斷了。可以試一下将catch的interrupt注釋掉進行驗證。

thread.sleep()方法由于中斷而抛出異常,此時,它會清除中斷标記,如果不加處理,那麼在下一次循環開始時,就無法不會這個中斷,是以在異常進行中,再次設定中斷标志位。

等待(wait)和通知(notify)

===================

wait和notify不是在thread類中的方法,而是在object類中,意味着任何對象都能調用這兩個方法。

如果一個線程調用了wait()方法,那麼它就會計入object對象的等待隊列。這個等待隊列中,可能會有多個線程,因為系統運作多個線程同時等待同一個對象。當notify()被調用是,它就會從這個等待隊列中,随機選擇一個線程,并将其喚醒。但是這個選擇不是公平的,并不是先等待的線程會優先被選擇,這個選擇完全是随機的。

notifyall()方法會喚醒這個等待隊列的所有線程。

無論是wait()或者是notify()方法,必須包含在對應的synchronized語句中,無論是wait()或者notify()都需要首先擷取目标對象的一個螢幕。

而wait()方法執行後,會釋放這個螢幕,當被重新notify()後,要做的第一件事不是繼續執行後續的代碼,而是要嘗試重新擷取object的螢幕。如果暫時無法獲得,線程還必須要等待這個螢幕。當螢幕順利獲得後,才可以真正意義上的繼續執行。

wait()方法和sleep()的差別就是,wait會釋放對象的鎖,而sleep不會釋放鎖。

挂起(suspend)和繼續執行(resume)

========================

被挂起的線程必須要等到resume操作後,才能繼續指定、

但是已經被标注為廢棄方法,不推薦使用。因為suspend()在導緻線程暫停的同時,并不會去釋放任何資源。此時,任何線程想要通路被它暫用的鎖,都會備受牽連,導緻無法正常運作。直到對應的線程上進行了resume()操作,被挂起的線程才能繼續操作。但是如果resume操作在suspend之前就執行了,那麼被挂起的線程就很難有機會被繼續執行了。

如果想要實作suspend跟resume,可以通過wait跟notify進行使用。

等待線程結束(join)和謙讓(yield)

==========================

一個線程的輸入可能非常依賴于另外一個或者多個線程的輸出,是以,這個線程就需要等待依賴線程執行完畢,才能繼續執行。

public final void join() throws interruptedexception

public final synchronized void join(long millis) throws interruptedexception

第一個join()方法表示無限等待,它會一直阻塞目前線程,知道目标線程執行完畢。

第二個join()給出了一個最大等待時間,如果超過給定時間目标線程還在執行,目前線程也會因為“等不及”,而繼續往下執行。

join就是加入的意思,是以一個線程要加入另外一個線程,那麼最好的方法就是等着它一起走。

public class joinmain {

public volatile static int i = 0;

public static class addthread extends thread {

@override

public void run() {

for (i = 0; i < 1000000; i++);

addthread at =

new addthread();

at.start();

at.join();

system.out.println(i);

主函數中,如果不用join()等待addthread,那麼得到的i很可能是0或者一個非常小的數字。因為addthread還沒執行完,i的值就已經被輸出了。但使用join方法後,表示主線程願意等待addthread執行完畢,跟着addthread一起往前走,是以在join()傳回,addthread已經執行完成,故i總是1000000;

join的本質是讓調用線程wait()在目前線程對象執行個體上。

if (millis == 0) {

while (isalive()) {

wait(0);

可以看到,它調用線程在目前線程對象上進行等待。當執行完成後,被等待的線程會在退出前調用notifyall()通知所有的等待線程繼續執行。

是以需要注意,不要在應用程式中,在thread對象上使用類似wait()或者notify()等方法,因為這很有可能影響系統api的工作,或者被系統api所影響。

public static native void yield();

yield()這是個靜态方法。一旦執行,它會使目前線程讓出cpu。目前線程讓出cpu後,還會進行cpu資源的争奪,但是是否能被再次配置設定到,就不一定了。

???????volatile與java記憶體模型(jmm)

=================================

???????java記憶體模型都是圍繞着原子性,有序性,可見性展開。

java使用了一些特殊的操作或者關鍵字來什麼,告訴虛拟機,在這個地方,尤其注意,不能随意變動優化目标指令。volatile就是其中之一。

volatile:易變的,不穩定的。

當volatile去申明一個變量,就等于告訴虛拟機。這個變量極有可能會被某些線程修改。為了確定這個變量被修改後,應用程式範圍内所有線程都能夠“看到”。虛拟機就必須采用一些特殊的手段,保證這個變量的可見性等特點。

volatile并不能替代鎖。也無法保證一些符合操作的原子性。volatile無法保證i++原子性操作。

volatile能保障資料的可見性和有序性。

???????守護線程(daemon)

=======================

???????守護線程是一種特殊的線程,是系統的守護者,在背景默默地完成一些系統性的服務。比如垃圾回收線程,jit線程就可以了解為守護線程。

線程的優先級

======

java使用1~10表示線程優先級。數字越大優先級越高。**???????**

同步方法以及同步塊

=========

線程同步

由于同一程序的多個線程共享同一塊存儲空間,在帶來友善的同時,也帶來了通路沖突問題,為了保障資料在方法中被通路時的正确性,在通路時加入鎖機制synchronized,當一個線程獲得對象的排他鎖,獨占資源,其他線程必須等待,使用後是否鎖即可。但是存在以下問題:

一個線程持有鎖會導緻其他所有需要此鎖的線程挂起。

在多線程競争下,加鎖,釋放鎖會導緻比較多的上下文切換和排程延時,引起性能問題

如果一個優先級高的線程等待一個優先級低的線程釋放鎖,會導緻優先倒置,引起性能問題。

關鍵字synchronized的作用是實作線程間的同步。它的工作是對同步的代碼加鎖,使得每一次,隻能有一個線程進入同步塊,進而保證線程間的安全性。

指定加鎖對象:對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。

直接作用于執行個體方法:相當于對目前執行個體加鎖,進入同步代碼前要獲得目前執行個體的鎖。

直接作用于靜态方法:相當于對目前類加鎖,進入同步代碼塊要獲得目前類的鎖。

synchronized除了保證線程同步,還可以保證線程之間的可見性和有序性。從可見性上來說,synchronized可以完全代替volatile,隻是使用上沒那麼友善。就有序性而言,由于synchronized限制每次隻有一個線程可以通路同步塊,無論同步塊内的代碼如何被亂序執行,隻要保證串行語義一緻性,那麼執行結果總是一樣的。

同步方法

可以通過private關鍵字來保證資料對象隻能被方法通路,是以隻需要針對方法提出一套機制,這套機制就是synchronized關鍵字。有synchronized方法和synchronized塊

synchronized方法控制對“對象”的通路,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞,方法一旦執行,就會獨占該鎖,直到方法傳回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行

缺陷:若将一個大的方法申明為synchronized将會影響效率。

同步塊

同步塊:synchronized(obj){}

obj 稱之為?同步螢幕

obj可以使任何對象,推薦使用共享資源作為同步螢幕

同步方法中無需指定同步螢幕,因為同步方法的同步螢幕就是this,就是這個對象本身,或者是class

同步螢幕的執行過程

第一個線程通路,鎖定同步螢幕,執行其中代碼

第二個線程通路,返現同步螢幕被鎖定,無法通路