Java-Review-Note——4.多線程

Java-Review-Note——4.多線程

標(biāo)簽: JavaStudy


PS:本來是分開三篇的,后來想想還是整理成一篇了定嗓,多線程看得夠嗆蜕琴,只能說,很多東西還需要實(shí)踐...
Σ(⊙▽⊙"a

程序宵溅,進(jìn)程奸绷,線程及多線程的理解

  • 程序:為了完成特定任務(wù),用某種語言編寫的一組指令集合(一組靜態(tài)代碼)
  • 進(jìn)程運(yùn)行中的程序层玲,系統(tǒng)調(diào)度與資源分配的一個獨(dú)立單位,操作系統(tǒng)會為每個進(jìn)程分配
    一段內(nèi)存空間;程序的依次動態(tài)執(zhí)行辛块,經(jīng)歷代碼的加載畔派,執(zhí)行,執(zhí)行完畢的完整過程润绵。
  • 線程:進(jìn)程的子集线椰,比進(jìn)程更小的執(zhí)行單位,每個進(jìn)程可能有多條線程尘盼,
    線程需要放在一個進(jìn)行中才能執(zhí)行憨愉;線程由程序負(fù)責(zé)管理而進(jìn)程則由系統(tǒng)進(jìn)行調(diào)度卿捎。
  • 多線程的理解并行執(zhí)行多條指令配紫,將CPU時間片按照調(diào)度算法分配給各個線程,
    實(shí)際上是分時執(zhí)行午阵,只是切換的時間很短躺孝,用戶感覺到"同時"而已。

線程的生命周期

注意:可以調(diào)isAlive()判斷線程是否死亡底桂,如果對已死亡的線程調(diào)start()方法會拋出
IllegalThreadStateException異常植袍!


創(chuàng)建線程的三種方式


1.繼承Thread類創(chuàng)建

流程:繼承Thread類,重寫run()方法籽懦,實(shí)例化線程對象于个,調(diào)start()方法從而啟動run()方法。

示例代碼

class MyThread extends Thread {
    public MyThread() {
        super("MyThread");  //標(biāo)識進(jìn)程名
        System.out.println("新建了線程:" + getName()); //getName()方法可獲得線程名
    }
    public void run() { 
        for(int i = 1;i <= 3;i++) {
            System.out.println(Thread.currentThread().getName() +" : " + i );  
            try {  sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  
        }
    }
}
//測試類
public class ThreadTest1 {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        MyThread mt = new MyThread();
        mt.start();
        //這里演示的是線程運(yùn)行完猴才打印主線程中的語句暮顺,join()讓異步執(zhí)行的線程
        //改成同步執(zhí)行厅篓,直到這個線程退出,程序才會繼續(xù)執(zhí)行
        try { mt.join(); }catch(InterruptedException e){ e.printStackTrace(); }
        System.out.println(t.getName() + " 打印完畢!");
    }
}

運(yùn)行結(jié)果


2.實(shí)現(xiàn)Runnable接口創(chuàng)建

流程:實(shí)現(xiàn)Runnable接口拖云,覆蓋run()方法贷笛,實(shí)例化自定義線程類對象,實(shí)例化Thread對象宙项,
將自定義線程對象作為參數(shù)傳給Thread對象乏苦,調(diào)用Thread對象的start()方法啟動run()方法。

示例代碼

class MyThread implements Runnable {
    public void run() {
        for(int i = 1;i <= 3;i++) {
            System.out.println(Thread.currentThread().getName() +" : " + i );
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread(),"MyThread");
        thread.start();
        System.out.println(Thread.currentThread().getName() + " 打印完畢尤筐!");
    }
}

運(yùn)行結(jié)果


3.實(shí)現(xiàn)Callable泛型接口創(chuàng)建

流程:實(shí)現(xiàn)Callable<T>泛型接口汇荐,重寫call()方法,然后使用Future或FutureTask來創(chuàng)建盆繁。
簡述:Callable和Future是jdk 1.5后引入的掀淘,引入目的是:解決兩種普通創(chuàng)建
Thread無法直接獲取執(zhí)行結(jié)果的缺陷;Callable接口中只聲明了一個call()的方法油昂,
返回類型就是Callable傳進(jìn)來的V類型革娄,一般情況下是配合ExecutorService來使用倾贰。
Future接口是對于具體的Runnable或Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消(cancel),
查詢是否取消成功(isCancelled)拦惋,查詢是否完成(isDone)匆浙,獲取查詢結(jié)果(get)
該方法會阻塞直到任務(wù)返回結(jié)果,另外get還有一個方法get(long timeout, TimeUnit unit)厕妖,
如果在指定時間內(nèi)沒獲取到結(jié)果首尼,就會返回null,也就是說Future提供三種功能:
判斷任務(wù)是否完成言秸,能否中斷任務(wù)软能,能夠獲取任務(wù)的執(zhí)行結(jié)果。
因?yàn)镕uture是一個接口举畸,無法直接用來創(chuàng)建對象查排,因此有了FutureTask。
FutureTask俱恶,實(shí)現(xiàn)了RunnableFuture<V>接口雹嗦,而RunnableFuture<V>接口接口繼承了
Runnable和Future<V>接口,所以FutureTask既可以作為Runnable被線程執(zhí)行合是,又可以作為
Future得到Callable的返回值了罪。

使用示例

實(shí)現(xiàn)Callable接口

class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("子線程正在計(jì)算...");
        int sum = 0;
        for(int i = 0;i < 100;i++) { sum += i; }
        return sum;
    }
}

Future :

    ExecutorService executor = Executors.newCachedThreadPool();
    MyThread myThread = new MyThread();
    Future<Integer> result = executor.submit(myThread);
    executor.shutdown();    //如果不調(diào)shutdown方法,executor會一直等待運(yùn)行聪全,即使沒線程
    try { 
        System.out.println("result運(yùn)行結(jié)果:" + result.get()); 
        } catch (InterruptedException e) { e.printStackTrace();
        } catch (ExecutionException e) { e.printStackTrace(); 
    }

FutureTask

    //第一種ExecutorService
    ExecutorService executor = Executors.newCachedThreadPool();
    MyThread thread = new MyThread();
    FutureTask<Integer> result = new FutureTask<>(thread);
    executor.submit(result);
    executor.shutdown();
    //第二種Thread
    MyThread myThread = new MyThread();
    FutureTask<Integer> futureTask = new FutureTask<>(myThread);
    Thread thread = new Thread(futureTask);
    thread.start();
    try {
        System.out.println("result運(yùn)行結(jié)果:" + result.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

運(yùn)行結(jié)果


多線程集錦

1.線程執(zhí)行的順序

多個線程同時執(zhí)行又叫線程并發(fā)泊藕,如果當(dāng)前有多個線程在并發(fā)執(zhí)行的話,多次運(yùn)行
的結(jié)果可能是不唯一的难礼,Java對于線程啟動后唯一能保證的就是:
每個線程都被啟動并且結(jié)束娃圆,但對于哪個線程先執(zhí)行,何時執(zhí)行蛾茉,都沒保證讼呢。

2.線程的優(yōu)先級

操作系統(tǒng)中的線程是具有優(yōu)先級,Java運(yùn)行環(huán)境采用的是固定優(yōu)先級調(diào)度算法
(Fixed Priority Scheduling)某一時刻有多個線程在運(yùn)行谦炬,JVM會選擇優(yōu)先級最高
的線程運(yùn)行悦屏,優(yōu)先級較高的線程會搶占cpu時間,相同優(yōu)先級的線程可能順序執(zhí)行键思,
也可能分時執(zhí)行础爬,取決于本地操作系統(tǒng)的線程調(diào)度策略喧枷,雖然說在任意時刻牲蜀,應(yīng)該
是最高優(yōu)先級的線程在運(yùn)行,但是這樣是不能保證的甩鳄,調(diào)度程序有可能選擇優(yōu)先級
較低的線程以避免饑餓(starvation)赔桌。搶占(pre-empt)策略
當(dāng)一個優(yōu)先級更高的進(jìn)程進(jìn)行可運(yùn)行狀態(tài)時發(fā)生搶占供炎,終止當(dāng)正在運(yùn)行的進(jìn)程而立即
去執(zhí)行優(yōu)先級更高的線程渴逻,而兩個相同優(yōu)先級的線程則采用循環(huán)執(zhí)行策略(round-robin);

3.Java中的線程優(yōu)先級

Java中線程的優(yōu)先級從0-10,整數(shù)碱茁,數(shù)值越大裸卫,優(yōu)先級越大,默認(rèn)線程優(yōu)先級為5纽竣,
可調(diào)用:setPriority()改變線程優(yōu)先級,或調(diào)用:getPriority()獲得線程的優(yōu)先級茧泪,
另外Java還提供了幾個線程優(yōu)先級的字段供使用:
MIN_PRIORITY(最低優(yōu)先級0)蜓氨;MAX_PRIORITY(最高優(yōu)先級10);NORM_PRIORITY(默認(rèn)優(yōu)先級5)
另外队伟,只能說優(yōu)先級高的線程更有可能獲得CPU穴吹,但也不能說優(yōu)先級較低的線程就
永遠(yuǎn)最后執(zhí)行,這不是絕對的嗜侮,可以理解成設(shè)置進(jìn)程優(yōu)先級只是給系統(tǒng)提供一個參考港令。

4.Java提供的進(jìn)程協(xié)作相關(guān)的方法

使用優(yōu)先級不能保證并發(fā)執(zhí)行的線程順序,但是Java也給我們提供了一些線程協(xié)作相關(guān)的方法:

Thread類中

  • run():線程執(zhí)行業(yè)務(wù)邏輯的地方锈颗,想讓線程執(zhí)行的代碼就寫在這里顷霹。
  • start():線程開始執(zhí)行,調(diào)用線程對象中的run()方法击吱,只能調(diào)用一次淋淀,多次啟動
    同一個線程,會拋出IllegalThreadStateException異常覆醇。
  • sleep():讓當(dāng)前線程休眠(堵塞)一段時間朵纷,但不釋放持有資源(對象鎖),如果此時
    的線程已經(jīng)被別的經(jīng)常中斷的話永脓,會拋出InterruptedException異常袍辞,另外調(diào)用該方法時
    需要對異常進(jìn)行捕獲。
  • join():在一個線程中調(diào)用第二個線程的join方法常摧,會導(dǎo)致當(dāng)前線程堵塞搅吁,直到第二
    個線程執(zhí)行完畢或者超過設(shè)置的等待毫秒數(shù)。
  • yield():暫停當(dāng)前線程排宰,把執(zhí)行機(jī)會讓給優(yōu)先級相同或更高的線程執(zhí)行似芝,不會進(jìn)入
    堵塞狀態(tài),直接強(qiáng)制切換為就緒狀態(tài)板甘,因此可能剛切換完yield方法党瓮,線程又獲得了處理器
    資源,然后又繼續(xù)執(zhí)行了盐类,該方法沒有聲明拋出任何異常寞奸。另外呛谜,如果沒有優(yōu)先級相同或者
    更高的線程,yield()方法什么都不做枪萄。

Object類中

疑問:為何這三個方法在Object類中隐岛?
答:每個對象都擁有一個monitor(鎖),讓當(dāng)前線程等待某個對象的鎖瓷翻,應(yīng)該通過這個對象操作聚凹,
而不是用當(dāng)前線程來操作,因?yàn)楫?dāng)前線程可能等待的事多個線程的鎖齐帚,用線程來操作妒牙,會非常復(fù)雜。

  • wait():讓當(dāng)前線程等待对妄,直到通過notify()和notifyAll()或等待時間結(jié)束湘今,當(dāng)前
    線程進(jìn)入等待隊(duì)列時,當(dāng)前的線程必須獲得該對象的內(nèi)部鎖剪菱,因此調(diào)用wait()方法必須在同
    步塊或同步方法中進(jìn)行摩瞎,如果該線程沒獲得對象鎖的話,是會拋出IllegalMonitorStateException
    異常的孝常,另外和sleep()不同旗们,wait()方法會讓出對象鎖,然后進(jìn)入等待狀態(tài)茫因;
  • notify():通知隨機(jī)喚醒一個在等待隊(duì)列中等待鎖的線程蚪拦,使該線程從堵塞狀態(tài)
    切換到就緒狀態(tài);同樣調(diào)用該方法的線程需要獲得對象鎖冻押,所以也需要些在同步塊/方法中進(jìn)行驰贷。
  • notifyAll():喚醒等待隊(duì)列中等待對象鎖的所有線程,但是也是只有一個會拿到對象鎖洛巢,
    至于是誰拿到鎖就看系統(tǒng)的調(diào)度了括袒。

幾個不安全,不推薦的方法

  • stop():停止線程稿茉,可能導(dǎo)致ThreadDeath異常锹锰,導(dǎo)致程序崩潰。
  • interrupt():終端線程漓库。
  • suspend()/resume():和wait恃慧,notify差不多,但容易引起死鎖渺蒿,不建議使用痢士。

Java 1.5 新增Condition接口

用來代替wait(),notify()茂装,notifyAll()實(shí)現(xiàn)實(shí)現(xiàn)線程間的協(xié)作怠蹂,使用await()善延,signal(),
signalAll()來實(shí)現(xiàn)線程間的協(xié)作更加安全高效城侧;Condition依賴于Lock接口易遣,調(diào)用Lock對象的
newCondition()可以生成Condition,Condition的await()嫌佑,signal()和signalAll()方法調(diào)用豆茫,
需要在lock()和unlock()之間使用


5.線程同步安全問題

當(dāng)有兩個或以上線程在同一時刻訪問操作同一資源屋摇,可能會帶來一些問題澜薄,比如:
數(shù)據(jù)庫表中不允許插入重復(fù)數(shù)據(jù),線程1,2都得到了數(shù)據(jù)X摊册,然后線程1,2同時查詢了
數(shù)據(jù)庫,發(fā)現(xiàn)沒有數(shù)據(jù)X颊艳,接著兩線程都往數(shù)據(jù)庫中插入X茅特,然后就GG了,這就是線程
安全問題棋枕,而這里的數(shù)據(jù)庫資源我們又稱為:臨界資源(共享資源)

6.如何解決線程安全問題

基本所有并發(fā)模式在解決線程安全問題時白修,都采用"系列化訪問臨界資源"的方式,
就是:同一時刻重斑,只能有一個線程訪問臨界資源兵睛,也成"同步互斥訪問"。通常就是
在操作臨界資源的代碼前加一個鎖窥浪,當(dāng)有線程訪問資源祖很,獲取這個所,其他線程無法
訪問漾脂,只能等待(堵塞)假颇,等這個線程訪問完臨界資源,然后釋放鎖骨稿,供其他線程繼續(xù)訪問笨鸡。
而Java中提供兩種方案來實(shí)現(xiàn)同步互斥訪問:synchronizedLock,等下詳細(xì)講坦冠。

7.與鎖相關(guān)的特殊情況:死鎖形耗,饑餓與活鎖

每個對象都擁有一個鎖,當(dāng)多個線程辙浑,操作涉及到多個鎖激涤,就可能會出現(xiàn)這三種情況:

死鎖(DeadLock)

兩個或以上進(jìn)程(線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象例衍,
如果無外力作用昔期,他們將繼續(xù)這樣僵持下去已卸;簡單點(diǎn)說:兩個人互相持有對方想要的資源,
然后每一方都不愿意放棄自己手上的資源硼一,就一直那樣僵持著累澡。

死鎖發(fā)生的條件

互斥條件(臨界資源);
請求和保持條件(請求資源但不釋放自己暫用的資源)般贼;
不剝奪條件(線程獲得的資源只有線程使用完后自己釋放愧哟,不能被其他線程剝奪);
環(huán)路等待條件:在死鎖發(fā)生時哼蛆,必然存在一個"進(jìn)程-資源環(huán)形鏈"蕊梧,t1等t2,t2等t1腮介;

如何避免死鎖

破壞四個條件中的一個或多個條件肥矢,常見的預(yù)防方法有如下兩種:
有序資源分配法:資源按某種規(guī)則統(tǒng)一編號,申請時必須按照升序申請:
1.屬于同一類的資源要一次申請完叠洗;2.申請不同類資源按照一定的順序申請甘改。
銀行家算法:就是檢查申請者對資源的最大需求量,如果當(dāng)前各類資源都可以滿足的
申請者的請求灭抑,就滿足申請者的請求十艾,這樣申請者就可很快完成其計(jì)算,然后釋放它占用
的資源腾节,從而保證了系統(tǒng)中的所有進(jìn)程都能完成忘嫉,所以可避免死鎖的發(fā)生。
理論上能夠非常有效的避免死鎖案腺,但從某種意義上說庆冕,缺乏使用價值,因?yàn)楹苌儆羞M(jìn)程
能夠知道所需資源的最大值救湖,而且進(jìn)程數(shù)目也不是固定的愧杯,往往是不斷變化的,
況且原本可用的資源也可能突然間變得不可用(比如打印機(jī)損壞)鞋既。

饑餓(starvation)與餓死(starve to death)

資源分配策略有可能是不公平的力九,即不能保證等待時間上界的存在,即使沒有發(fā)生死鎖邑闺,
某些進(jìn)程可能因長時間的等待跌前,對進(jìn)程推進(jìn)與相應(yīng)帶來明顯影響,此時的進(jìn)程就是
發(fā)生了進(jìn)程饑餓(starvation)陡舅,當(dāng)饑餓達(dá)到一定程序即此時進(jìn)程即使完成了任務(wù)也
沒有實(shí)際意義時抵乓,此時稱該進(jìn)程被餓死(starve to death),典型的例子:
文件打印,采用短文件優(yōu)先策略灾炭,如果短文件太多茎芋,長文件會一直推遲,那還打印個毛蜈出。

活鎖(LiveLock)

特殊的饑餓田弥,一系列進(jìn)程輪詢等待某個不可能為真的條件為真,此時進(jìn)程不會進(jìn)入blocked狀態(tài)铡原,
但會占用CPU資源偷厦,,活鎖還有幾率能自己解開燕刻,而死鎖則無法自己解開只泼。(例子:都覺得對方
優(yōu)先級比自己搞,相互謙讓卵洗,導(dǎo)致無法使用某資源)请唱,簡單避免死鎖的方法:先來先服務(wù)策略


8.守護(hù)線程

也叫后臺線程过蹂,是一種為其他線程提供服務(wù)的一種線程籍滴;當(dāng)虛擬機(jī)檢測到?jīng)]有用戶進(jìn)程可服務(wù)的
時候,就會退出當(dāng)前應(yīng)用程序的運(yùn)行榴啸,比如JVM的gc(垃圾回收線程),當(dāng)我們程序中不再有任何
Thread的時候晚岭,垃圾回收就沒事做了鸥印,即使還有其他后臺線程,但是此時的JVM還是會退出坦报!
將一個線程設(shè)置為后臺線程:setDaemon(boolean)
判斷一個線程是否為后臺線程:isDaemon()


9.線程并發(fā)的問題

開始的問題:臨時數(shù)據(jù)存放在內(nèi)存中库说,而CPU執(zhí)行指令比內(nèi)存讀寫快太多!
解決方法:CPU中使用高速緩存片择,將需要數(shù)據(jù)從內(nèi)存復(fù)制一份到緩存中潜的,運(yùn)算時CPU直接讀緩存,
運(yùn)算結(jié)束后字管,再將緩存中的數(shù)據(jù)刷新回內(nèi)存中啰挪。

又有問題:單線程倒沒什么,多線程的話嘲叔,切線程處于不同的CPU中亡呵,每個線程都擁有自己的
高速緩存,這樣可能會出現(xiàn)緩存不一致的問題硫戈,比如內(nèi)存中的i = 0锰什,然后在兩個線程中都執(zhí)行
i = i + 1;分別在兩個線程中執(zhí)行,最后的結(jié)果可能是1汁胆,而不是2梭姓。
硬件層次的解決方案在總線加LOCK#鎖的方式緩存一致性協(xié)議(Intel的MESI協(xié)議)

10.并發(fā)編程的三個概念

  • 原子性:一個操作或多個操作,要么全部執(zhí)行嫩码,并且執(zhí)行過程不會被任何因素打斷誉尖;
    要么就都不執(zhí)行,比如:銀行轉(zhuǎn)賬的栗子谢谦。
  • 可見性:當(dāng)多個線程訪問同一個變量時释牺,一個線程修改了這個變量的值,其他線程能夠
    立即看到修改后的值回挽。
  • 有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行没咙。可能你覺得自己程序怎么寫的執(zhí)行
    順序就是怎樣千劈,只能說naive祭刚!有個名詞叫:指令重排序!一般來說處理器為了提高程序
    的運(yùn)行效率墙牌,可能會對輸入的代碼進(jìn)行優(yōu)化涡驮,它不保證程序中每個語句的執(zhí)行先后順序一致
    但是他會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的喜滨!另外處理器在重排序的
    時候還會考慮指令的數(shù)據(jù)依賴性捉捅,如果指令2需要用到指令1的結(jié)果,那么處理器會保證
    指令1會在指令2之前執(zhí)行虽风,指令重排序不會音箱單線程的執(zhí)行棒口,但是在多線程并發(fā)的時候可能
    會影響結(jié)果的正確性。

綜上:想要保證并發(fā)程序能夠正確執(zhí)行辜膝,必須保持原子性无牵,可見性,有序性厂抖,只有有一個沒
保證茎毁,就有可能會導(dǎo)致運(yùn)行不正確。

11.Java中對并發(fā)編程的保證與8條先行發(fā)生原則

Java內(nèi)存模型規(guī)定所有變量都是存儲在主存中忱辅,每個線程都有自己的工作內(nèi)存(類似于前面的高速
緩存)七蜘,線程對變量的所有操作必須在工作內(nèi)存中,而不能直接對主存進(jìn)行操作墙懂,且每個線程不能
訪問其他線程的工作內(nèi)存崔梗。

Java中語言本身對原子性,可見性垒在,有序性提供了哪些保證蒜魄?

  • 原子性:Java內(nèi)存模型只保證了基本讀取和賦值原子性操作扔亥,如果要實(shí)現(xiàn)
    更大范圍操作的原子性,可以通過synchronized和Lock來實(shí)現(xiàn)谈为。
  • 可見性:Java提供了volatile關(guān)鍵字保證可見性旅挤,當(dāng)一個共享變量被
    volatile修飾時,它會保證修改的值會立即被更新到主存伞鲫,當(dāng)有其他線程需要讀取時粘茄,
    他會去內(nèi)存中讀取新值。另外秕脓,通過synchronized和Lock也能夠保證可見性柒瓣。
  • 有序性:Java內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序吠架,以通過volatile
    關(guān)鍵字來保證一定的"有序性"芙贫,通過synchronized和Lock也能夠保證有序性。
    另外傍药,Java內(nèi)存模型具有一些先天的"有序性"磺平,即不需要通過任何手段就能夠得到保證的有序性
    這個通常也稱為happens-before原則拐辽,如果兩個操作的執(zhí)行次序無法從happens-before原則
    推導(dǎo)出來拣挪,那么它們就不能保證它們的有序性,虛擬機(jī)可以隨意地對它們進(jìn)行重排序俱诸。

8條happens-before原則(先行發(fā)生原則)

  • 程序次序規(guī)則:一個線程內(nèi)菠劝,按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作睁搭。
    該規(guī)則用來保證程序在單線程中執(zhí)行結(jié)果的正確性闸英,但無法保證程序在多線程中執(zhí)行的正確性目尖。
  • 鎖定規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖的lock操作纫谅。
    就是同一個鎖如果處于被鎖定狀態(tài)蛮粮,那么必須先對鎖進(jìn)行釋放操作,后面才能繼續(xù)進(jìn)行l(wèi)ock操作遇伞。
  • volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作。
    直觀解釋:若一個線程先去寫一個變量捶牢,然后一個線程去讀取鸠珠,那么寫入操作肯定先與讀操作執(zhí)行。
  • 傳遞規(guī)則:如果操作A先行發(fā)生于操作B秋麸,而操作B又先行發(fā)生于操作C渐排,則可以得出操作A先
    行發(fā)生于操作C。說明happens-before原則具備傳遞性灸蟆。
  • 線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每一個動作驯耻。
  • 線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生
  • 線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()
    方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
  • 對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始

12.線程并發(fā)的經(jīng)典問題:生產(chǎn)者消費(fèi)者問題

問題概述

兩個共享固定緩沖區(qū)大小的線程可缚,生產(chǎn)者線程負(fù)責(zé)生產(chǎn)一定量的數(shù)據(jù)放入緩沖區(qū)霎迫,
而消費(fèi)者線程則負(fù)責(zé)消耗緩沖區(qū)中的數(shù)據(jù),關(guān)鍵問題是需要保證
1.緩沖區(qū)滿的時候帘靡,生產(chǎn)者不再往緩沖區(qū)中填充數(shù)據(jù)
2.緩存區(qū)空的時候知给,消費(fèi)者不在消耗緩沖區(qū)中的數(shù)據(jù)

可以用簡單的兩種套路來模擬這個問題:
synchronized + wait() + notify()方式 或 Lock + Condition接口的await()與signal()實(shí)現(xiàn)
等下細(xì)講。


13.同步容器

Java集合容器中有四類:List(數(shù)組)描姚,Set(集合)涩赢,Queue(隊(duì)列)Map轩勘,前三個都繼承Collection接口筒扒,
Map本身是一個接口。我們平常使用的ArrayList赃阀,LinkedList霎肯,HashMap這些容器都是非線程安全的,并發(fā)訪問的時候可能會
有問題榛斯,然后Java給我們提供了兩類的同步容器:

  • Vector观游,StackHashTable(普通集合加了同步措施而已)
  • Collections類中提供的靜態(tài)工廠方法創(chuàng)建的類(不是Connection類M运住!王凑!提供了靜態(tài)工廠方法來創(chuàng)建同步容器搪柑,還提供
    了對集合或容器進(jìn)行排序工碾,查找等的操作。)
image_1aummto991fre1k4dnpv19mv3jk9.png-21.6kB
image_1aummto991fre1k4dnpv19mv3jk9.png-21.6kB

相比起非同步容器百姓,同步容器因?yàn)殒i的關(guān)系渊额,性能會稍弱。
另外同步容器也不一定是安全的垒拢,只能保證每個時刻只能有一個線程在訪問他旬迹,集合刪元素遍歷的例子,
有時為了保證線程安全求类,還需要在方法調(diào)用端做額外的同步措施奔垦!

另外,在對Vector等容器并發(fā)地進(jìn)行迭代修改時尸疆,會報(bào)ConcurrentModificationException異常椿猎!
原因是:Iterator執(zhí)行next方法時惶岭,調(diào)用checkForComodification()時expectedModCount與modCount不相等,
由于執(zhí)行remove()鸵贬,modCount每次循環(huán)值都會改變俗他,而expectedModCount并沒改變,所以拋異常阔逼!

解決方法

  • 1.使用Iterator提供的remove方法來刪除當(dāng)前元素兆衅。
  • 2.另外創(chuàng)建一個集合來存放要刪除的元素,然后調(diào)用removeAll統(tǒng)一刪除嗜浮。
  • 3.自己通過索引來刪除羡亩,要保證索引是正常的,比如刪除了4危融,你的索引要變回3畏铆;
    另外這些可能不是線程安全的,想保證多線程執(zhí)行也安全的話吉殃,可以:
  • 1.使用Iterator迭代的時候辞居,加同步鎖
  • 2.使用并發(fā)容器CopyOnWriteArrayList代替ArrayList和Vector。

14.并發(fā)容器

同步容器因?yàn)槭褂肧ynchronized進(jìn)行同步蛋勺,執(zhí)行讀寫都需要去獲取鎖瓦灶,并發(fā)的時候效率較低,
Jdk 1.5新增的concurrent提供了這些并發(fā)容器:

  • BlockingQueue接口:線程安全的堵塞式隊(duì)列抱完,線程安全的阻塞式隊(duì)列贼陶;當(dāng)隊(duì)列已滿時,向隊(duì)列
    添加會阻塞巧娱;當(dāng)隊(duì)列空時碉怔,取數(shù)據(jù)會阻塞。(非常適合消費(fèi)者-生產(chǎn)者模式)
    阻塞方式:put()禁添、take()撮胧。
    非阻塞方式:offer()、poll()老翘。
    實(shí)現(xiàn)類:基于數(shù)組的固定元素個數(shù)的ArrayBolockingQueue和基于鏈表結(jié)構(gòu)的不固定元素個
    數(shù)的LinkedBlockQueue類芹啥。
  • BlockingDeque接口: 與BlockingQueue相似,但可以對頭尾進(jìn)行添加和刪除操作的雙向隊(duì)列酪捡;
    方法分為兩類,分別在隊(duì)首和對尾進(jìn)行操作纳账。
    實(shí)現(xiàn)類:標(biāo)準(zhǔn)庫值提供了一個基于鏈表的實(shí)現(xiàn)逛薇,LinkedBlockgingDeque
  • ConcurrentMap接口: 繼承自java.util.Map接口
    putIfAbsent():只有在散列表不包含給定鍵時疏虫,才會把給定的值放入永罚。
    remove():刪除條目啤呼。
    replace(key,value):把value 替換到給定的key上。
    replace(key, oldvalue, newvalue):CAS的實(shí)現(xiàn)呢袱。
    實(shí)現(xiàn)類:ConcurrentHashMap
    創(chuàng)建時官扣,如果可以預(yù)估可能包含的條目個數(shù),可以優(yōu)化性能羞福。(因?yàn)閯討B(tài)調(diào)整所能包含的數(shù)目操作比較耗時
    這個HashMap也一樣惕蹄,只是多線程下更耗時)。創(chuàng)建時治专,預(yù)估進(jìn)行更新操作的線程數(shù)卖陵,這樣實(shí)現(xiàn)中會根據(jù)這
    個數(shù)把內(nèi)部空間劃分為對應(yīng)數(shù)量的部分。(默認(rèn)是16张峰,如果只有一個線程進(jìn)行寫操作泪蔫,其他都是讀取,
    那么把值設(shè)為1 可以提高性能)喘批。
    注:當(dāng)從集合中創(chuàng)建出迭代器遍歷Map元素時撩荣,不一定能看到正在添加的數(shù)據(jù),只能和集合保證弱一致性饶深。
    (當(dāng)然使用迭代器不會因?yàn)椴榭凑诟淖兊腗ap餐曹,而拋出java.util.ConcurrentModifycationException)
  • CopyOnWriteArrayList/CopyOnWriteArraySet:當(dāng)往容器中添加數(shù)據(jù)時,不是直接添加粥喜,而是將當(dāng)前容器進(jìn)行Copy
    然后往新容器里添加凸主,添加完后,再把舊容器的引用只想新容器额湘,讀寫分離的思想卿吐。可以參考CopyOnWriteArrayList寫個
    CopyOnWriteMap锋华,套路:在put和putAll的時候加synchronized鎖嗡官,創(chuàng)建新集合放值,然后舊的指向新的集合就好毯焕。
    注意:創(chuàng)建時初始化好大小衍腥,避免擴(kuò)容開銷;批量添加纳猫,減少容器的復(fù)制次數(shù)婆咸;只保證數(shù)據(jù)的最終一致性,不保證
    數(shù)據(jù)的實(shí)時一致性芜辕!

15.阻塞隊(duì)列

在解決生產(chǎn)者與消費(fèi)者問題時尚骄,我們的套路一般是對容器加鎖,然后判斷容器中數(shù)據(jù)滿和空的情況侵续,
然后喚醒或者阻塞生產(chǎn)者或者消費(fèi)者線程倔丈,有些麻煩憨闰,如果使用阻塞隊(duì)列,我們就不用關(guān)心那么多需五,
阻塞隊(duì)列會對訪問線程產(chǎn)生堵塞鹉动,比如當(dāng)?shù)仃?duì)列滿了,此時生產(chǎn)者線程會被阻塞宏邮,直到消費(fèi)者消費(fèi)
了隊(duì)列中的元素泽示,被堵塞的線程會自動喚醒。其實(shí)就是把wait()蜀铲,notify()這些集成到隊(duì)列中實(shí)現(xiàn)边琉。

幾種主要的阻塞隊(duì)列

同樣是java.util.concurrent包下提供的若干個阻塞隊(duì)列:

  • ArrayBlockingQueue:基于數(shù)組實(shí)現(xiàn)的,創(chuàng)建時需指定容量大小记劝,也可以指定公平性與非公平性变姨,
    默認(rèn)非公平,即不保證等待時間最長的隊(duì)列最優(yōu)先能夠訪問隊(duì)列厌丑。
  • LinkedBlockingQueue:基于鏈表實(shí)現(xiàn)的定欧,創(chuàng)建時不指定容量大小的話,默認(rèn)大小為Integer.MAX_VALUE怒竿。
    PriorityBlockingQueue:前面兩個都是先進(jìn)先出隊(duì)列砍鸠,而PriorityBlockingQueue是按照元素優(yōu)先級對
    元素進(jìn)行排序,按照優(yōu)先級順序出隊(duì)耕驰,即每次出隊(duì)的都是優(yōu)先級最高的元素爷辱;另外,該隊(duì)列是無界阻塞隊(duì)列朦肘,
    即容量沒有上線饭弓,而前兩種是有界隊(duì)列。
    DelayQueue:基于PriorityQueue媒抠,延時阻塞隊(duì)列弟断,隊(duì)列中的元素只有當(dāng)其延時時間到了,才能夠從隊(duì)列中獲取到
    該元素趴生,同樣是無界隊(duì)列阀趴,因此往隊(duì)列里插入元素(生產(chǎn)者)永遠(yuǎn)不會被阻塞,而只有獲取數(shù)據(jù)(消費(fèi)者)才
    會被阻塞苍匆。

堵塞隊(duì)列除了對非阻塞隊(duì)列的下述五個方法進(jìn)行了同步:

  • add(E e):添加元素到隊(duì)尾刘急,插入成功返回true,若插入失敗(隊(duì)滿)浸踩,拋出異常叔汁。
  • remove():移除隊(duì)首元素,移除成功返回true,若移除失敗(隊(duì)空)攻柠,拋出異常。
  • offer(E e):添加元素到隊(duì)尾后裸,插入成功返回true瑰钮,若插入失敗(隊(duì)滿),返回false微驶。
  • poll():移除并獲取隊(duì)首元素浪谴,成功返回元素,否則返回null因苹。
  • peek():獲取隊(duì)首元素苟耻,成功返回隊(duì)首元素,否則返回null扶檐。

還提供了另外4個非常有用的方法:

  • put(E e):向隊(duì)尾存入元素凶杖,隊(duì)滿則等待。
  • take():取隊(duì)首元素款筑,隊(duì)空則等待智蝠。
  • offer(E e,long timeout,TimeUnit unit):往隊(duì)尾存元素,隊(duì)滿等待一定時間奈梳,時間到后杈湾,若沒有插入成功,
    返回false攘须,否則返回true漆撞;
  • poll(long timeout,TimeUnit unit):取隊(duì)首元素,隊(duì)空等待一定時間于宙,時間到后浮驳,沒取到返回null,否則
    返回取得的元素限煞。

阻塞隊(duì)列適用于生產(chǎn)者-消費(fèi)者問題抹恳,因?yàn)椴恍枰賳为?dú)考慮同步和線程間通信的問題。


16.線程組

我們可以通過java.lang.ThreadGroup對線程進(jìn)行組操作署驻,每個線程都?xì)w屬于某個線程組管理的一員奋献。
在創(chuàng)建Thread實(shí)例時,如果沒有制定線程組參數(shù)旺上,則默認(rèn)屬于創(chuàng)建者線程所隸屬的線程組瓶蚂,這種隸屬
關(guān)系在創(chuàng)建新線程時指定,在線程的整個生命周期里都不能改變宣吱!比如我們在main()中創(chuàng)建的新線程窃这,
這個線程屬于main這個線程管理中的一員!

作用

簡化對多個線程的管理征候,對若干線程同時操作杭攻,比如:調(diào)用線程組的方法設(shè)置所有線程的優(yōu)先級祟敛,
調(diào)用線程組的犯法啟動或堵塞組中所有線程等;其實(shí)兆解,線程組的最重要意義是線程安全馆铁,Java默認(rèn)
創(chuàng)建的線程都是屬于系統(tǒng)線程組,而處于同一線程組中的線程可以互相修改對方數(shù)據(jù)锅睛,當(dāng)如果在不
同的線程組中埠巨,那么就不能"跨線程組"修改數(shù)據(jù),從一定程度上保證了數(shù)據(jù)的安全现拒。

線程組提供的操作

  • 集合管理方法:用于管理包含在線程組中的線程與子線程組
  • 組操作方法:設(shè)置或獲取線程對象的屬性
  • 組中所有偶線程的操作方法辣垒,針對組中所有線程與子線程執(zhí)行某一操作,比如:線程啟動印蔬,恢復(fù)
  • 訪問限制方法:基于線程組的成員關(guān)系

常見用法

  • 獲得當(dāng)前線程的線程組對象:ThreadGroup tGroup = Thread.currentThread().getThreadGroup();
  • 獲得線程組的名字:tGroup.getName();
  • 獲得線程中活動線程的數(shù)目:tGroup.activeCount();
  • 將線程組中每個活動線程復(fù)制到線程數(shù)組中:tGroup.enumerate(Thread list[])
  • 獲得本線程組的父線程組:tGroup.getParent();
  • 判斷某個線程組是否為守護(hù)線程組:tGroup.isDaemon();

使用示例

public class ThreadGroupTest {
    public static void main(String[] args) {
        ThreadGroup tGroup = new ThreadGroup("自定義線程組");
        Thread t1 = new Thread(tGroup,"自定義線程1");
        Thread t2 = new Thread(tGroup,"自定義線程2");
        System.out.println("線程組的初始最大優(yōu)先級:" + tGroup.getMaxPriority());
        System.out.println(t1.getName() + "的初始優(yōu)先級:" + t1.getPriority());
        System.out.println(t2.getName() + "的初始優(yōu)先級:" + t2.getPriority());
        t1.setPriority(9);  //設(shè)置t1的優(yōu)先級為9
        tGroup.setMaxPriority(8);   //設(shè)置線程組的優(yōu)先級為8
        System.out.println("線程組的新最大優(yōu)先級:" + tGroup.getMaxPriority());
        System.out.println(t1.getName() + "的新優(yōu)先級" + t1.getPriority());
        t2.setPriority(10); //設(shè)置t2的優(yōu)先級為10
        System.out.println(t2.getName() + "的新優(yōu)先級" + t2.getPriority());
        System.out.println(tGroup.toString());
    }
}

運(yùn)行結(jié)果


17.線程池

引入

直接創(chuàng)建的線程在調(diào)用完start()方法結(jié)束后勋桶,線程就結(jié)束了,而線程的創(chuàng)建與結(jié)束都需要耗費(fèi)
一定的系統(tǒng)時間侥猬,如果并發(fā)的線程數(shù)量很多的話哥遮,不停的創(chuàng)建和銷毀線程會消耗大量的時間,
效率就有些低了陵究,我們想讓線程在執(zhí)行完任務(wù)以后并不銷毀眠饮,而是讓他進(jìn)入休眠狀態(tài),然后
繼續(xù)執(zhí)行其他任務(wù)铜邮,當(dāng)需要用到這個線程再喚醒仪召,Java中使用線程池可以達(dá)到這樣的效果。

ThreadPoolExecutor類

繼承AbstractExecutorService類松蒜,并提供四種構(gòu)造方法(前三其實(shí)都是調(diào)用第四個進(jìn)行初始化
工作的):

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { }

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { }

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { }

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { }

參數(shù)解析

  • corePoolSize:核心池大小扔茅,創(chuàng)建線程池后,默認(rèn)池中沒有任何線程秸苗,而是等待有任務(wù)來才去
    創(chuàng)建線程執(zhí)行任務(wù)召娜,除非調(diào)用prestartAllCoreThreads()或prestartCoreThread()方法預(yù)創(chuàng)建線
    程,就是在沒有任務(wù)來之前就創(chuàng)建corePollSize個線程或者一個線程惊楼,當(dāng)池中線程數(shù)目達(dá)到
    corePoolSize后玖瘸,會把后到達(dá)的任務(wù)放到緩存隊(duì)列中。
  • maximumPoolSize:線程池最大線程數(shù)檀咙,表示線程池中最多創(chuàng)建多少個線程雅倒。
  • keepAliveTime:線程沒有任務(wù)執(zhí)行最多保持多久時間會終止,只有當(dāng)池中線程數(shù)大于
    corePoolSize才會起作用弧可,直到池中線程數(shù)不超過corePoolSize蔑匣。但是如果調(diào)用了
  • allowCoreThreadTimeOut(boolean)方法,即使池中線程不大于CorePoolsize,
    該參數(shù)也會起作用裁良,知道池中線程數(shù)為0凿将。
  • unit:keepAliveTime的時間單位,比如毫秒:TimeUnit.MILLISECONDS 等价脾。
  • workQueue:堵塞隊(duì)列丸相,用來存儲等待執(zhí)行的任務(wù),一般來說有以下幾種選擇:
    ArrayBlockingQueue(基于數(shù)組的并發(fā)堵塞隊(duì)列)
    LinkedBlockingQueue(基于鏈表的FIFO堵塞隊(duì)列)
    PriorityBlockingQueue(帶優(yōu)先級的無界堵塞隊(duì)列)
    SynchronousQueue(并發(fā)同步堵塞隊(duì)列)
  • threadFactory:線程工廠彼棍,用于創(chuàng)建線程。
  • handler:表示拒絕處理任務(wù)時的策略膳算,有四種取值:
    ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常座硕。
    ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常涕蜂。
    ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù)华匾,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
    ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)

線程池相關(guān)的幾個類之間的關(guān)系

使用示例

MyThread.java

public class MyThread implements Runnable {
    private int threadNum;

    public MyThread(int threadNum) {
        this.threadNum = threadNum;
    }

    @Override
    public void run() {
        System.out.println("線程:" + threadNum + "開始執(zhí)行...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("線程:" + threadNum + "執(zhí)行完畢...");
    }
}

ThreadTest.java 測試類:

public class ThreadTest {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200,
                TimeUnit.MICROSECONDS, new ArrayBlockingQueue<Runnable>(5));
        for (int i = 0; i < 15; i++) {
            MyThread myThread = new MyThread(i);
            executor.execute(myThread);
            System.out.println("線程池中線程數(shù)目:" + executor.getPoolSize() + " 等待執(zhí)行的任務(wù)數(shù)目:" + executor.getQueue().size());
        }
        executor.shutdown();
    }
}

另外,Java文檔中并不提倡我們直接使用ThreadPoolExecutor机隙,而是使用Executors提供的幾個靜態(tài)方法來創(chuàng)建線程池:

  • Executors.newSingleThreadExecutor():創(chuàng)建容量為1的線程池蜘拉,可以理解為串行執(zhí)行所有任務(wù),任務(wù)執(zhí)行順序與提交順序相同有鹿。
  • Executors.newFixedThreadPool():創(chuàng)建固定容量的線程池
  • Executors.newCachedThreadPool():創(chuàng)建可以緩存的線程池旭旭,緩沖池容量大小為Inter.MAX_VALUE,可以靈活的往池中添加線程葱跋,
    如果線程空閑超過了指定時間持寄,該工作線程會終止,終止后如果你提交了任務(wù)娱俺,線程池會重新創(chuàng)建一個工作線程稍味。
  • Executors.newScheduledThreadPool():創(chuàng)建不限容量的線程池,支持定時及周期性執(zhí)行任務(wù)的需求荠卷,多了個schedule(thread,time)
    延時執(zhí)行的方法模庐。

當(dāng)然,如果這個幾個線程池都滿足不了你的話油宜,你可以繼承ThreadPoolExecutor類重寫掂碱。


18.Timer和TimerTask

Timer是Jdk提供的定時器類,延時或者重復(fù)執(zhí)行任務(wù)慎冤,使用時候會主線程外開啟
單獨(dú)的線程來執(zhí)行定時任務(wù)顶吮,可以指定執(zhí)行一次或重復(fù)多次。TimerTask是實(shí)現(xiàn)了
Runnable接口的抽象類粪薛,代表一個可以被Timer執(zhí)行的任務(wù)难审。

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    public void run() { ... timer.cancle(); }
},延時時間)

終止Timer的幾個方式

  • 1.調(diào)用timer的cancle();
  • 2.將timer線程設(shè)置為守護(hù)線程蝶防;
  • 3.所有任務(wù)執(zhí)行完后谓松,刪除timer對象的引用凤薛,線程也會被終止;
  • 4.System.exit()終止程序昏名;

另外,schedule保證每次延遲間隔固定,而scheduleAtFixedRate則可能因?yàn)槟硞€調(diào)度
時間太長息罗,而縮短間隔的方式,保證下一次調(diào)度在預(yù)定時間執(zhí)行才沧,比如迈喉,每隔3s調(diào)度一次:
正常都是:0,3,6,9,假如第二次調(diào)度花了2s温圆,前者會變成:0,3+2,8,11挨摸,而后者會壓縮間隔,
保證調(diào)度時間岁歉,變成:0,3+2,6,9得运,另外還要注意Timer不保證任務(wù)執(zhí)行的十分準(zhǔn)確!


19.三個并發(fā)輔助類:CountDownLatch锅移,CyclicBarrier和Semaphore

CountDownLatch(類似于計(jì)時器熔掺,比如有任務(wù)A,需等其他4個任務(wù)執(zhí)行完畢才執(zhí)行非剃,就可以用上)
使用方法如下:

CountDownLatch latch = new CountDownLatch[2]置逻;  //初始值
latch.await();  //調(diào)用了這個方法的那個線程會被掛起,直到count = 0才繼續(xù)執(zhí)行备绽,也可以設(shè)置時間诽偷,
                //超時count還沒變0就會繼續(xù)執(zhí)行
latch.countDown();  //count值減1

CyclicBarrier(回環(huán)柵欄,讓一組線程等待到某個狀態(tài)再全部同時執(zhí)行疯坤,所有等待線程被釋放后报慕,CyclicBarrier可以重用)
比如:若干個線程需要進(jìn)行寫操作,并且想所有線程都達(dá)到某個狀態(tài)才能執(zhí)行后續(xù)任務(wù)压怠,此時可以用CyclicBarrier眠冈。
使用方法如下:

CyclicBarrier barrier = new CyclicBarrier(parties); //參數(shù)是指定多個線程達(dá)到某狀態(tài)
                                                    //如果你想執(zhí)行完任務(wù)后,做其他操作可加個Runnable的參數(shù)菌瘫。
barrier.await();    //掛起當(dāng)前線程蜗顽,直到到達(dá)某個狀態(tài)再同時執(zhí)行,同樣可以設(shè)置時間雨让,超時直接
                    //執(zhí)行已經(jīng)到達(dá)這個狀態(tài)的線程

PS:個人簡單理解:調(diào)了await()會掛起這個線程雇盖,然后+1,直到結(jié)果等于parties栖忠,再繼續(xù)執(zhí)行掛起線程的后續(xù)部分崔挖。

Semaphore(信號量贸街,控制某資源同時被幾個線程訪問的類,與鎖類似)
使用方法如下:

Semaphore semaphore = new Semaphore(5); //設(shè)置多少個線程同時訪問狸相,可選參數(shù)boolean fair表示
                                        //是否公平薛匪,即等待越久越先獲得許可
semaphore.acquire();    //獲取一個許可
semaphore.acquire(int);    //獲取多個許可
semaphore.release() //釋放一個許可
semaphore.release(int) //釋放多個許可

//acquire獲取許可的方式會堵塞,就是沒有拿到的話會一直等待脓鹃,如果想立即得到結(jié)果
可調(diào)用:tryAcquire()

semaphore.availablePermits()    //獲得可用許可數(shù)目


20.ThreadLocal(線程本地存儲)

作用:ThreadLocal的作用是提供線程內(nèi)的局部變量逸尖,這種變量在線程的生命周期內(nèi)起作用,
減少同一個線程內(nèi)多個函數(shù)或者組件之間一些公共變量傳遞的復(fù)雜度瘸右,同時隔離其他線程娇跟。
你可以:

  • 重寫initialValue()方法來設(shè)置ThreadLocal的初始值
  • get():獲得當(dāng)前線程的ThreadLocal的值
  • set():設(shè)置當(dāng)前線程的ThreadLocal的值
  • remove():刪除當(dāng)前線程的ThreadLocal綁定的值,某些情況下需要手動調(diào)用該函數(shù)太颤,防止內(nèi)存泄露苞俘。

用法示例

public class ThreadTest {
    private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        for (int i =0;i < 5;i++) {
            ThreadTest test = new ThreadTest();
            new Thread(test.new MyThread(i)).start();
        }
    }

    class MyThread implements Runnable {
        private int index;

        public MyThread(int index) {
            this.index = index;
        }

        @Override
        public void run() {
            System.out.println("線程" + index + "的初始value:" + value.get());
            for (int i = 0; i < 10; i++) {
                value.set(value.get() + i);
            }
            System.out.println("線程" + index + "的累加value:" + value.get());
            try {
                Thread.sleep(1000l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運(yùn)行結(jié)果

ThreadLocal的實(shí)現(xiàn)原理

每個Thread維護(hù)一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實(shí)例本身栋齿,
value是真正需要存儲的Object。

更多詳細(xì)內(nèi)容可見:[Java并發(fā)包學(xué)習(xí)七]解密ThreadLocal


細(xì)講與代碼實(shí)現(xiàn)


1.synchronized同步方法或代碼塊

在Java中每個對象都擁有一個monitor(互斥鎖標(biāo)記)襟诸,也稱為監(jiān)視器瓦堵,當(dāng)多個線程訪問某
個對象時,線程只有獲得該對象的鎖才能訪問歌亲。使用synchronized關(guān)鍵字來獲取對象上的鎖菇用,
可應(yīng)用在方法級別(粗粒度鎖)代碼塊級別(細(xì)粒度鎖)

同步方法

比如: public synchronized void save(){}

同步代碼塊

比如:synchronized(資源對象){ }

類鎖

每個類都有一個類鎖陷揪,用于類的靜態(tài)方法或者一個類的class對象上的(單例)惋鸥,類的對象實(shí)例
可以有很多個,但是每個類只有一個class對象悍缠,所以不同對象實(shí)例的對象鎖是互不干擾
的卦绣,但是每個類只有一個類鎖。類鎖與對象鎖是兩種不同的鎖飞蚓,控制著不同區(qū)域滤港,互不干擾,
統(tǒng)一趴拧,線程獲得對象鎖的同時溅漾,也可以獲取類鎖,同時獲得兩個鎖是允許的著榴。
用法比如:public static synchronized insert() {}; synchronized(類.class)

注意事項(xiàng)

  • 1.當(dāng)有線程在訪問對象的synchronized方法添履,其他線程不能訪問該對象的其他
    synchronized方法!但可以訪問非synchronized方法脑又。
  • 2.對于synchronized方法或者synchronized代碼塊暮胧,當(dāng)出現(xiàn)異常時锐借,JVM會自動
    釋放當(dāng)前線程占用的鎖,因此不會由于異常導(dǎo)致出現(xiàn)死鎖現(xiàn)象叔壤。
  • 3.一個線程可以獲得多個鎖標(biāo)記瞎饲,一個對象最多只能把鎖標(biāo)記給一個線程
    synchronized是以犧牲程序效率為代價的炼绘,因此應(yīng)該盡量控制互斥代碼塊的范圍嗅战。
  • 4.方法的Synchronized特性本身不會被繼承,只能覆蓋俺亮。
  • 5.線程因未拿到鎖標(biāo)記而發(fā)生的堵塞不同于基本狀態(tài)中的堵塞驮捍,等待的線程
    會進(jìn)入該對象的鎖池(放置等待獲取鎖標(biāo)記的線程)中,鎖池中哪個線程拿到鎖
    標(biāo)記由系統(tǒng)決定脚曾。

2.Lock(鎖)

Synchronized的缺陷

使用Synchronized獲取鎖的線程釋放鎖的兩種情況

  • 1.獲取鎖的線程執(zhí)行完該代碼塊东且,然后線程釋放對鎖的占有;
  • 2.線程執(zhí)行發(fā)生異常本讥,此時JVM會讓線程自動釋放鎖珊泳;

如果是IO等待或其他原因(調(diào)sleep方法)被堵塞了,但又沒釋放鎖拷沸,只能一直等待色查;
另外當(dāng)多個線程讀寫文件時,讀和寫會沖突撞芍,寫和寫會沖突秧了,但是讀與讀不該沖突。
而使用Lock可以解決上述問題序无,而且Lock還可以知道有沒有獲得鎖验毡,Lock類可以實(shí)
現(xiàn)同步訪問,另外synchronized不需要用戶自己手動去釋放鎖帝嗡,而Lock需由用戶去
手動釋放鎖晶通,若果沒有主動釋放的話,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象哟玷。

Lock源碼解析

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
  • lock():獲取鎖录择,如果鎖已被其他線程獲取,則進(jìn)行等待
  • unlock():釋放鎖碗降,Lock必須主動去釋放鎖隘竭,所以Lock必須在try-catch中進(jìn)行,在finally中釋放讼渊。
  • tryLock():嘗試獲取鎖动看,獲取成功返回true,獲取失敗返回false爪幻,無論如何會立即返回菱皆,而不會
    在因拿不到鎖就一直在那里等待须误;如果是有參的那個,拿不到鎖會等待一段時間仇轻,時間到了也會立即返回京痢。
  • lockInterruptibly():當(dāng)通過該方法來獲取鎖時,如果已經(jīng)有某個進(jìn)程持有了這個鎖篷店,而另一線
    程需要等待祭椰,那么對另一個線程調(diào)用interrupt方法中斷等待過程。

ReentrantLock(可重入鎖疲陕,獨(dú)占鎖):唯一實(shí)現(xiàn)Lock接口的類

示例代碼

public class Main {
    private ArrayList<Integer> arrayList = new ArrayList<>();
    private Lock lock = new ReentrantLock();
    
    public static void main(String[] args) {
        final Main main = new Main();
        for(int i = 0;i < 2;i++) {
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    main.lock(Thread.currentThread());
                }
            }.start();
        }
    }

    /**lock()*/
    public void lock(Thread thread) {
        lock.lock();
        duplicated(thread);
    }

    /**tryLock()*/
    public void tryLock(Thread thread) {
        if(lock.tryLock()) {
            duplicated(thread);
        } else {
            System.out.println(thread.getName()+"獲取鎖失敗");
        }
    }

    //相同代碼
    public void duplicated(Thread thread) {
        try {
            System.out.println(thread.getName() + "得到了鎖");
            for(int i=0;i<5;i++) { arrayList.add(i); }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}

運(yùn)行結(jié)果

ReadWriteLock接口

讀寫操作分開成兩個鎖方淤,一個資源能夠被多個讀線程訪問,或者被一個寫線程訪問蹄殃,
但是不能同時存在讀寫線程携茂,使用場合:共享資源被大量讀取操作,只有少量寫操作(修改數(shù)據(jù))

ReentrantReadWriteLock(讀寫鎖):

readLock()和writeLock()用來獲取讀鎖和寫鎖诅岩,讀鎖是共享鎖讳苦,能同時被多個線程獲取吩谦;
寫入鎖是獨(dú)占鎖鸳谜,只能被一個線程鎖獲取。


3.鎖的相關(guān)概念

  • 1.可重入鎖:synchronized和ReentrantLock都是可重鎖逮京,比如線程執(zhí)行某個synchronized方法
    在這個方法里會調(diào)該類中的另一個synchronized方法卿堂,此時不用重復(fù)申請鎖束莫,可以直接執(zhí)行該方法懒棉。
  • 2.可中斷鎖:可以中斷的鎖,在Java中览绿,synchronized就不是可中斷鎖策严,而Lock是可中斷鎖。
  • 3.公平鎖:盡量以請求鎖的順序來獲取鎖饿敲,當(dāng)這個所釋放時妻导,等待時間最久的線程(最先請求)
    會獲得該鎖,這就是公平鎖怀各,非公平鎖無法保證按順序倔韭,就可能導(dǎo)致某個/一些線程永遠(yuǎn)獲取不到鎖。
    synchronized就是非公平鎖瓢对,而對于ReentrantLock和ReentrantReadWriteLock寿酌,它默認(rèn)情況下是非
    公平鎖,但是可以設(shè)置為公平鎖硕蛹,構(gòu)建的時候傳參(true表示公平鎖醇疼,false為非公平鎖硕并,用無參構(gòu)
    造方法,則是非公平鎖)秧荆,另外記住一點(diǎn):ReentrantReadWriteLock并未實(shí)現(xiàn)Lock接口倔毙,它實(shí)現(xiàn)的是
    ReadWriteLock接口。
  • 4.讀寫鎖:將對一個資源(比如文件)的訪問分成了2個鎖乙濒,一個讀鎖和一個寫鎖陕赃,
    ReadWriteLock就是讀寫鎖,它是一個接口琉兜,ReentrantReadWriteLock實(shí)現(xiàn)了這個接口凯正。
    可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖豌蟋。

4.生產(chǎn)者與消費(fèi)者的幾種代碼實(shí)現(xiàn)

synchronized + wait() + notify()方式

實(shí)現(xiàn)核心:定義一個倉庫類廊散,對于生產(chǎn)和消耗方法加synchronized鎖;
定義兩個線程,生產(chǎn)者和消費(fèi)者梧疲,對于滿或空的情況進(jìn)行判斷允睹,wait()和notify();

產(chǎn)品類:Product.java

public class Product {
    private int productId = 0;
    public Product() {  }
    public Product(int productId) {
        this.productId = productId;
    }
    public int getProductId() {
        return productId;
    }
    public void setProductId(int productId) {
        this.productId = productId;
    }
    @Override
    public String toString() {
        return "Product{" +
                "productId=" + productId +
                '}';
    }
}

倉庫類:WareHouse.java

public class WareHouse {
    private int base = 0;
    private int top = 0;
    private Product[] products = new Product[10];
    public synchronized void produce(Product product) {
        notify();
        while (top == products.length) {
            try {
                System.out.println("倉庫已滿幌氮,暫停生產(chǎn)缭受,等待消費(fèi)者消費(fèi)...");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        products[top] = product;
        top ++;
    }
    public synchronized Product consume() {
        Product product = null;
        while (top == base) {
            notify();
            try {
                System.out.println("倉庫已空,暫停消費(fèi)该互,等待生產(chǎn)者生產(chǎn)...");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        top--;
        product = products[top];
        products[top] = null;
        return product;
    }
}

生產(chǎn)者線程:Producer.java:

public class Producer implements Runnable {
    private String produceName;
    private WareHouse wareHouse;
    public Producer() { }
    public Producer(String produceName, WareHouse wareHouse) {
        this.produceName = produceName;
        this.wareHouse = wareHouse;
    }
    public String getProduceName() {
        return produceName;
    }
    public void setProduceName(String produceName) {
        this.produceName = produceName;
    }
    @Override
    public void run() {
        int i = 0;
        int j = 0;
        while (j < 100) {
            i++;
            j++;
            Product product = new Product(i);
            wareHouse.produce(product);
            System.out.println(getProduceName() + "生產(chǎn)了" + product);
            try {
                Thread.sleep(200l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消費(fèi)者線程:Consumer.java

public class Consumer implements Runnable{
    private String consumerName = null;
    private WareHouse wareHouse = null;
    public Consumer() { }
    public Consumer(String consumerName, WareHouse wareHouse) {
        this.consumerName = consumerName;
        this.wareHouse = wareHouse;
    }
    public String getConsumerName() {
        return consumerName;
    }
    public void setConsumerName(String consumerName) {
        this.consumerName = consumerName;
    }
    @Override
    public void run() {
        int j = 0;
        while (j < 100) {
            j++;
            System.out.println(getConsumerName() + "消費(fèi)了" + wareHouse.consume());
            try {
                Thread.sleep(300l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

測試類:Test.java

public class Test {
    public static void main(String[] args) {
        WareHouse wareHouse = new WareHouse();
        Producer producer = new Producer("生產(chǎn)者",wareHouse);
        Consumer consumer = new Consumer("消費(fèi)者",wareHouse);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
    }
}

Lock + Condition接口的await()與signal()實(shí)現(xiàn)

和上面代碼沒太大區(qū)別米者,只是改了下倉庫類:ConditionWareHouse.java:
這里可以只用一個Condition,把signal改成signalAll()即可宇智。

public class ConditionWareHouse {
    private LinkedList<Product> products = new LinkedList<>();
    private static final int MAX_SIZE = 10; //倉庫容量
    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();
    public  void produce(Product product) {
        lock.lock();
        try {
            while (products.size() == MAX_SIZE) {
                System.out.println("倉庫已滿蔓搞,暫停生產(chǎn),等待消費(fèi)者消費(fèi)...");
                notFull.await();
            }
            products.add(product);
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public  Product consume() {
        lock.lock();
        Product product = null;
        try {
            while (products.size() == 0) {
                System.out.println("倉庫已空随橘,暫停消費(fèi)喂分,等待生產(chǎn)者生產(chǎn)...");
                notEmpty.await();
            }
            product = products.removeLast();
            notFull.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return product;
    }
}

使用堵塞隊(duì)列實(shí)現(xiàn)

public class ThreadTest {

    private static final int MAX_SIZE = 10; //倉庫容量
    private  ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(MAX_SIZE);

    public static void main(String[] args) {
        ThreadTest test = new ThreadTest();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();
        new Thread(producer).start();
        new Thread(consumer).start();
    }

    class Producer implements Runnable {
        @Override
        public void run() {
            for(int i =0;i < 100;i++) {
                try {
                    queue.put(i);
                    System.out.println("往倉庫中放入元素(" + i + ")剩余容量為:" + (MAX_SIZE - queue.size()));
                    Thread.sleep(50l);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            for(int i =0;i < 100;i++) {
                try {
                    queue.take();
                    System.out.println("從倉庫中取走元素(" + i + ")剩下元素:" + queue.size());
                    Thread.sleep(100l);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.volatile關(guān)鍵字詳解

深入剖析volatile關(guān)鍵字

  • volatile關(guān)鍵字的兩層語義可見性禁止進(jìn)行指令重排序机蔗。
    使用volatile修飾的變量蒲祈,會強(qiáng)制將修改的值立即寫入主存,當(dāng)寫入的時候萝嘁,
    會導(dǎo)致工作內(nèi)存緩存的變量的緩存行無效梆掸,線程會再去主存中讀取變量。
  • volatile保證原子性嗎牙言?
    volatile無法保證對變量的任何操作都是原子性的酸钦,比如自增操作不是原子性的,
    可以通過synchronized和Lock來實(shí)現(xiàn)嬉挡,另外jdk 1.5在java.util.concurrent.atomic包下
    提供了一些原子操作類钝鸽,比如自增AtomicInteger汇恤。
  • volatile能保證有序性嗎?volatile關(guān)鍵字禁止指令重排序有兩層意思:
    1.當(dāng)程序執(zhí)行到volatile變量的讀寫操作拔恰,在其前面的操作的更改肯定全部已經(jīng)進(jìn)行因谎,
    且結(jié)果已經(jīng)對后面的操作可見;在其后面的操作肯定還沒有進(jìn)行颜懊;
    2.在進(jìn)行指令優(yōu)化時财岔,不能將在對volatile變量訪問的語句放在其后面執(zhí)行,
    也不能把volatile變量后面的語句放到其前面執(zhí)行河爹。

volatile的原理與實(shí)現(xiàn)機(jī)制

摘自:《深入理解Java虛擬機(jī)》:
"觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時所生成的匯編代碼發(fā)現(xiàn)匠璧,加入volatile關(guān)鍵
字時,會多出一個lock前綴指令” lock前綴指令實(shí)際上相當(dāng)于一個內(nèi)存屏障(也成內(nèi)存柵欄)咸这,
內(nèi)存屏障會提供3個功能:
1)它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置夷恍,也不會把前面
的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時媳维,在它前面的操作已經(jīng)全部完成酿雪;
2)它會強(qiáng)制將對緩存的修改操作立即寫入主存;
3)如果是寫操作侄刽,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效指黎。

volatile關(guān)鍵字的使用場景

狀態(tài)量標(biāo)記

//狀態(tài)量標(biāo)記
volatile boolean flag = false;
while(!flag){
    doSomething();
}
public void setFlag() {
    flag = true;
}
//多線程
volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

雙重校驗(yàn)鎖

class Singleton{
    private volatile static Singleton instance = null;
    private Singleton() { }
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

MusicTime

:靜心~大悲咒

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="http://music.163.com/outchain/player?type=2&id=344719&auto=1&height=66"></iframe>


本文內(nèi)容部分摘自

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市州丹,隨后出現(xiàn)的幾起案子醋安,更是在濱河造成了極大的恐慌,老刑警劉巖墓毒,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吓揪,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚁鳖,警方通過查閱死者的電腦和手機(jī)磺芭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門赁炎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醉箕,“玉大人,你說我怎么就攤上這事徙垫〖タ悖” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵姻报,是天一觀的道長己英。 經(jīng)常有香客問我,道長吴旋,這世上最難降的妖魔是什么损肛? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任厢破,我火速辦了婚禮,結(jié)果婚禮上治拿,老公的妹妹穿的比我還像新娘摩泪。我一直安慰自己,他們只是感情好劫谅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布见坑。 她就那樣靜靜地躺著,像睡著了一般捏检。 火紅的嫁衣襯著肌膚如雪荞驴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天贯城,我揣著相機(jī)與錄音熊楼,去河邊找鬼。 笑死能犯,一個胖子當(dāng)著我的面吹牛孙蒙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播悲雳,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼挎峦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了合瓢?” 一聲冷哼從身側(cè)響起坦胶,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晴楔,沒想到半個月后顿苇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡税弃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年纪岁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片则果。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡幔翰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出西壮,到底是詐尸還是另有隱情遗增,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布款青,位于F島的核電站做修,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饰及,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一蔗坯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧燎含,春花似錦步悠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铣除,卻和暖如春谚咬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尚粘。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工择卦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人郎嫁。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓秉继,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泽铛。 傳聞我的和親對象是個殘疾皇子尚辑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 871評論 0 1
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,798評論 1 19
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司盔腔,掛了不少杠茬,但最終還是拿到小米、百度弛随、阿里瓢喉、京東、新浪舀透、CVTE栓票、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,184評論 11 349
  • 聽說走贪,你很早就喜歡上了我。 “你好链烈,我叫夏夕顏” 這是她第一次對我說的話厉斟。 夕顏挚躯,夕顏强衡,真的很美,亦如她一樣码荔。 她...
    笑妖精閱讀 634評論 0 0
  • Intellij導(dǎo)入Maven工程時有時候會出現(xiàn)包解析不出來的問題漩勤,java類會顯示錯誤感挥,import也說找不到包...
    picco閱讀 433評論 0 0