Java十七篇:多線程
加強自身學習,提高自身素質。 積累工作經(jīng)驗,改進工作方法,向周圍同志學習,注重別人優(yōu)點,學習他們處理問題的方法,查找不足,提高自己。
常常聽到進程,殺死這個進程。
問題:進程和線程有啥關系呢?
進程和線程
進程是程序的一次動態(tài)執(zhí)行過程,它需要經(jīng)歷從代碼加載,代碼執(zhí)行到執(zhí)行完畢的一個完整的過程,這個過程也是進程本身從產(chǎn)生,發(fā)展到最終消亡的過程。多進程操作系統(tǒng)能同時達運行多個進程(程序),由于 CPU 具備分時機制,所以每個進程都能循環(huán)獲得自己的CPU 時間片。由于 CPU 執(zhí)行速度非??欤沟盟谐绦蚝孟袷窃谕瑫r運行一樣。
多線程是實現(xiàn)并發(fā)機制的一種有效手段。進程和線程一樣,都是實現(xiàn)并發(fā)的一個基本單位。線程是比進程更小的執(zhí)行單位,線程是進程的基礎之上進行進一步的劃分。所謂多線程是指一個進程在執(zhí)行過程中可以產(chǎn)生多個更小的程序單元,這些更小的單元稱為線程,這些線程可以同時存在,同時運行,一個進程可能包含多個同時執(zhí)行的線程。進程與線程的區(qū)別如圖所示:

Java 給多線程編程提供了內置的支持。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務。
多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。
這里定義和線程相關的另一個術語 - 進程:一個進程包括由操作系統(tǒng)分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行后才能結束。
多線程能滿足程序員編寫高效率的程序來達到充分利用 CPU 的目的。
一個線程的生命周期
線程是一個動態(tài)執(zhí)行的過程,它也有一個從產(chǎn)生到死亡的過程。
下圖顯示了一個線程完整的生命周期。
新建狀態(tài):
使用?new?關鍵字和?Thread?類或其子類建立一個線程對象后,該線程對象就處于新建狀態(tài)。它保持這個狀態(tài)直到程序?start()?這個線程。
就緒狀態(tài):
當線程對象調用了start()方法之后,該線程就進入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊列中,要等待JVM里線程調度器的調度。
運行狀態(tài):
如果就緒狀態(tài)的線程獲取 CPU 資源,就可以執(zhí)行?run(),此時線程便處于運行狀態(tài)。處于運行狀態(tài)的線程最為復雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。
阻塞狀態(tài):
如果一個線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運行狀態(tài)進入阻塞狀態(tài)。在睡眠時間已到或獲得設備資源后可以重新進入就緒狀態(tài)。可以分為三種:
等待阻塞:運行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進入到等待阻塞狀態(tài)。
同步阻塞:線程在獲取 synchronized 同步鎖失敗(因為同步鎖被其他線程占用)。
其他阻塞:通過調用線程的 sleep() 或 join() 發(fā)出了 I/O 請求時,線程就會進入到阻塞狀態(tài)。當sleep() 狀態(tài)超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉入就緒狀態(tài)。
死亡狀態(tài):
一個運行狀態(tài)的線程完成任務或者其他終止條件發(fā)生時,該線程就切換到終止狀態(tài)。
線程的優(yōu)先級
每一個 Java 線程都有一個優(yōu)先級,這樣有助于操作系統(tǒng)確定線程的調度順序。
Java 線程的優(yōu)先級是一個整數(shù),其取值范圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默認情況下,每一個線程都會分配一個優(yōu)先級 NORM_PRIORITY(5)。
具有較高優(yōu)先級的線程對程序更重要,并且應該在低優(yōu)先級的線程之前分配處理器資源。但是,線程優(yōu)先級不能保證線程執(zhí)行的順序,而且非常依賴于平臺。
創(chuàng)建線程的方式
Java 提供了四種創(chuàng)建線程的方法:
通過實現(xiàn) Runnable 接口;
通過繼承 Thread 類本身;
通過 Callable 和 Future 創(chuàng)建線程。
通過線程池去創(chuàng)建線程
1.實現(xiàn)Runnable接口
publicclass Day1 implements Runnable {
? ?private String name; ? ? ? // 表示線程的名稱
? ?public Day1(String name) {
? ? ? ?this.name = name; ? ? ?// 通過構造方法配置name屬性
? ?}
? ?@Override
? ?public void run() {
? ? ? ?for (int i = 0; i < 10; i++) {
? ? ? ? ? ?System.out.println(name+"運行,i="+i);
? ? ? ?}
? ?}
publicclass RunnableDemo01 {
? ?public static void main(String[] args) {
? ? ? ?Day1 mt1 = new Day1("線程A "); ? ?// 實例化對象
? ? ? ?Day1 mt2 = new Day1("線程B "); ? ?// 實例化對象
? ? ? ? Thread t1 = new Thread(mt1); ? ? ? // 實例化Thread類對象
? ? ? ? Thread t2 = new Thread(mt2); ? ? ? // 實例化Thread類對象
? ? ? ? t1.start(); ? ?// 啟動多線程
? ? ? ? t2.start(); ? ?// 啟動多線程
? ?}
}
結果:
線程A 運行,i=0
線程A 運行,i=1
線程A 運行,i=2
線程A 運行,i=3
線程A 運行,i=4
線程A 運行,i=5
線程A 運行,i=6
線程A 運行,i=7
線程A 運行,i=8
線程A 運行,i=9
線程B 運行,i=0
線程B 運行,i=1
線程B 運行,i=2
線程B 運行,i=3
線程B 運行,i=4
線程B 運行,i=5
線程B 運行,i=6
線程B 運行,i=7
線程B 運行,i=8
線程B 運行,i=9
2.繼承Thread類
package com.zhsj.test;
/**
* @author 劉良琪
* @version V1.0
* <p> </p>
* @Package com.zhsj.test
* @date 2021/12/20 21:23
*/
publicclass MyThread extends Thread{
? ?private ?String name;
? ?public MyThread(String name){
? ? ? ?this.name = name;
? ?}
? ?@Override
? ?public void run(){
? ? ? ?for (int i = 0; i < 10; i++) {
? ? ? ? ? ?System.out.println(name+"運行,i="+i);
? ? ? ?}
? ?}
? ?publicstaticclass ThreadDemo02{
? ? ? ?public static void main(String[] args) {
? ? ? ? ? ?MyThread mt1 = new MyThread("線程A "); ? ?// 實例化對象
? ? ? ? ? ?MyThread mt2 = new MyThread("線程B "); ? ?// 實例化對象
? ? ? ? ? ?mt1.start(); ? // 啟動多線程
? ? ? ? ? ?mt2.start(); ? ?// 啟動多線程
? ? ? ?}
? ?}
}
結果
線程A 運行,i=0
線程B 運行,i=0
線程B 運行,i=1
線程B 運行,i=2
線程B 運行,i=3
線程A 運行,i=1
線程A 運行,i=2
線程A 運行,i=3
線程A 運行,i=4
線程A 運行,i=5
線程A 運行,i=6
線程A 運行,i=7
線程A 運行,i=8
線程A 運行,i=9
線程B 運行,i=4
線程B 運行,i=5
線程B 運行,i=6
線程B 運行,i=7
線程B 運行,i=8
線程B 運行,i=9
從程序可以看出,現(xiàn)在的兩個線程對象是交錯運行的,哪個線程對象搶到了 CPU 資源,哪個線程就可以運行,所以程序每次的運行結果肯定是不一樣的,在線程啟動雖然調用的是 start() 方法,但實際上調用的卻是 run() 方法定義的主體。
Thread 類和 Runnable 接口區(qū)別
通過 Thread 類和 Runable 接口都可以實現(xiàn)多線程,那么兩者有哪些聯(lián)系和區(qū)別呢?下面我們觀察 Thread 類的定義。
publicclass Thread extends Object implements Runnable
從 Thread 類的定義可以清楚的發(fā)現(xiàn),Thread 類也是 Runnable 接口的子類,但在Thread類中并沒有完全實現(xiàn) Runnable 接口中的 run() 方法,下面是 Thread 類的部分定義。
Private Runnable target;
public Thread(Runnable target,String name){
? ? ?init(null,target,name,0);
?}
?private void init(ThreadGroup g,Runnable target,String name,long stackSize){
? ? ?...
? ? ?this.target=target;
?}
?public void run(){
? ? if(target!=null){
? ? ? ?target.run();
? ? }
}
從定義中可以發(fā)現(xiàn),在 Thread 類中的 run() 方法調用的是 Runnable 接口中的 run() 方法,也就是說此方法是由 Runnable 子類完成的,所以如果要通過繼承 Thread 類實現(xiàn)多線程,則必須覆寫 run()。
實際上 Thread 類和 Runnable 接口之間在使用上也是有區(qū)別的,如果一個類繼承 Thread類,則不適合于多個線程共享資源,而實現(xiàn)了 Runnable 接口,就可以方便的實現(xiàn)資源的共享。
3.通過 Callable 和 Future 創(chuàng)建線程
創(chuàng)建 Callable 接口的實現(xiàn)類,并實現(xiàn) call() 方法,該 call() 方法將作為線程執(zhí)行體,并且有返回值。
創(chuàng)建 Callable 實現(xiàn)類的實例,使用 FutureTask 類來包裝 Callable 對象,該 FutureTask 對象封裝了該 Callable 對象的 call() 方法的返回值。
使用 FutureTask 對象作為 Thread 對象的 target 創(chuàng)建并啟動新線程。
調用 FutureTask 對象的 get() 方法來獲得子線程執(zhí)行結束后的返回值。
publicclass CallableThreadTest implements Callable<Integer> {
? ?public static void main(String[] args) ?
? ?{ ?
? ? ? ?CallableThreadTest ctt = new CallableThreadTest(); ?
? ? ? ?FutureTask<Integer> ft = new FutureTask<>(ctt); ?
? ? ? ?for(int i = 0;i < 100;i++) ?
? ? ? ?{ ?
? ? ? ? ? ?System.out.println(Thread.currentThread().getName()+" 的循環(huán)變量i的值"+i); ?
? ? ? ? ? ?if(i==20) ?
? ? ? ? ? ?{ ?
? ? ? ? ? ? ? ?new Thread(ft,"有返回值的線程").start(); ?
? ? ? ? ? ?} ?
? ? ? ?} ?
? ? ? ?try
? ? ? ?{ ?
? ? ? ? ? ?System.out.println("子線程的返回值:"+ft.get()); ?
? ? ? ?} catch (InterruptedException e) ?
? ? ? ?{ ?
? ? ? ? ? ?e.printStackTrace(); ?
? ? ? ?} catch (ExecutionException e) ?
? ? ? ?{ ?
? ? ? ? ? ?e.printStackTrace(); ?
? ? ? ?} ?
?
? ?}
? ?@Override
? ?public Integer call() throws Exception ?
? ?{ ?
? ? ? ?int i = 0; ?
? ? ? ?for(;i<100;i++) ?
? ? ? ?{ ?
? ? ? ? ? ?System.out.println(Thread.currentThread().getName()+" "+i); ?
? ? ? ?} ?
? ? ? ?return i; ?
? ?} ?
}
結果:
main 的循環(huán)變量i的值0
main 的循環(huán)變量i的值1
main 的循環(huán)變量i的值2
main 的循環(huán)變量i的值3
main 的循環(huán)變量i的值4
main 的循環(huán)變量i的值5
main 的循環(huán)變量i的值6
main 的循環(huán)變量i的值7
main 的循環(huán)變量i的值8
main 的循環(huán)變量i的值9
有返回值的線程 0
main 的循環(huán)變量i的值41
有返回值的線程 1
有返回值的線程 2
有返回值的線程 3
有返回值的線程 4
有返回值的線程 5
main 的循環(huán)變量i的值42
main 的循環(huán)變量i的值43
main 的循環(huán)變量i的值44
main 的循環(huán)變量i的值45
有返回值的線程 12
有返回值的線程 13
有返回值的線程 14
有返回值的線程 15
有返回值的線程 16
有返回值的線程 17
4.通過線程池來創(chuàng)建線程
創(chuàng)建線程池的正確方式:
避免使用Executors創(chuàng)建線程池主要是為了避免其中的默認實現(xiàn),可以改用ThreadPoolExecutor構造方法指定參數(shù)即可。

需要指定核心線程池的大小、最大線程池的數(shù)量、保持存活的時間、等待隊列容量的大小。在這種情況下一旦提交的線程數(shù)超過當前可用的線程數(shù)時就會拋出拒絕執(zhí)行的異常 java.util.concurrent.RejectedExecutionException 有界隊列已經(jīng)滿了便無法處理新的任務。
使用工具類來創(chuàng)建線程池:
除了自己定義的ThreadPool之外,還可以使用開源庫 apache guava等。
個人推薦使用guava的ThreadFactoryBuilder() 來創(chuàng)建線程池:

多線程
用多線程只有一個目的,那就是更好的利用cpu的資源,因為所有的多線程代碼都可以用單線程來實現(xiàn)。說這個話其實只有一半對,因為反應“多角色”的程序代碼,最起碼每個角色要給他一個線程吧,否則連實際場景都無法模擬,當然也沒法說能用單線程來實現(xiàn):比如最常見的“生產(chǎn)者,消費者模型”。
很多人都對其中的一些概念不夠明確,如同步、并發(fā)等等,讓我們先建立一個數(shù)據(jù)字典,以免產(chǎn)生誤會。
多線程:指的是這個程序(一個進程)運行時產(chǎn)生了不止一個線程
并行與并發(fā):
并行:多個cpu實例或者多臺機器同時執(zhí)行一段處理邏輯,是真正的同時。
并發(fā):通過cpu調度算法,讓用戶看上去同時執(zhí)行,實際上從cpu操作層面不是真正的同時。并發(fā)往往在場景中有公用的資源,那么針對這個公用的資源往往產(chǎn)生瓶頸,我們會用TPS或者QPS來反應這個系統(tǒng)的處理能力。

并發(fā)與并行
線程安全:經(jīng)常用來描繪一段代碼。指在并發(fā)的情況之下,該代碼經(jīng)過多線程使用,線程的調度順序不影響任何結果。這個時候使用多線程,我們只需要關注系統(tǒng)的內存,cpu是不是夠用即可。反過來,線程不安全就意味著線程的調度順序會影響最終結果,如不加事務的轉賬代碼:
void transferMoney(User from, User to, float amount){
?to.setMoney(to.getBalance() + amount);
?from.setMoney(from.getBalance() - amount);
}
同步:Java中的同步指的是通過人為的控制和調度,保證共享資源的多線程訪問成為線程安全,來保證結果的準確。如上面的代碼簡單加入
@synchronized
關鍵字。在保證結果準確的同時,提高性能,才是優(yōu)秀的程序。線程安全的優(yōu)先級高于性能。
好了,讓我們開始吧。我準備分成幾部分來總結涉及到多線程的內容:、
扎好馬步:線程的狀態(tài)
內功心法:每個對象都有的方法(機制)
太祖長拳:基本線程類
九陰真經(jīng):高級多線程控制類
扎好馬步:線程的狀態(tài)

線程狀態(tài)

線程狀態(tài)轉換
各種狀態(tài)一目了然,值得一提的是"blocked"這個狀態(tài):線程在Running的過程中可能會遇到阻塞(Blocked)情況
調用join()和sleep()方法,sleep()時間結束或被打斷,join()中斷,IO完成都會回到Runnable狀態(tài),等待JVM的調度。
調用wait(),使該線程處于等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(tài)(Runnable)
對Running狀態(tài)的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(tài)(Runnable)。
此外,在runnable狀態(tài)的線程是處于被調度的線程,此時的調度順序是不一定的。Thread類中的yield方法可以讓一個running狀態(tài)的線程轉入runnable。
內功心法:每個對象都有的方法(機制)
synchronized, wait, notify 是任何對象都具有的同步工具。讓我們先來了解他們

monitor
他們是應用于同步問題的人工線程調度工具。講其本質,首先就要明確monitor的概念,Java中的每個對象都有一個監(jiān)視器,來監(jiān)測并發(fā)代碼的重入。在非多線程編碼時該監(jiān)視器不發(fā)揮作用,反之如果在synchronized 范圍內,監(jiān)視器發(fā)揮作用。
wait/notify必須存在于synchronized塊中。并且,這三個關鍵字針對的是同一個監(jiān)視器(某對象的監(jiān)視器)。這意味著wait之后,其他線程可以進入同步塊執(zhí)行。
當某代碼并不持有監(jiān)視器的使用權時(如圖中5的狀態(tài),即脫離同步塊)去wait或notify,會拋出java.lang.IllegalMonitorStateException。也包括在synchronized塊中去調用另一個對象的wait/notify,因為不同對象的監(jiān)視器不同,同樣會拋出此異常。
再講用法:
synchronized單獨使用:
代碼塊:如下,在多線程環(huán)境下,synchronized塊中的方法獲取了lock實例的monitor,如果實例相同,那么只有一個線程能執(zhí)行該塊內容
publicclass Thread1 implements Runnable {
? Object lock;
? public void run() { ?
? ? ? synchronized(lock){
? ? ? ? ..do something
? ? ? }
? }
}
直接用于方法:相當于上面代碼中用lock來鎖定的效果,實際獲取的是Thread1類的monitor。更進一步,如果修飾的是static方法,則鎖定該類所有實例。
publicclass Thread1 implements Runnable {
? public synchronized void run() { ?
? ? ? ?..do something
? }
}
synchronized, wait, notify結合:典型場景生產(chǎn)者消費者問題
/**
? * 生產(chǎn)者生產(chǎn)出來的產(chǎn)品交給店員
? */
?public synchronized void produce()
?{
? ? ?if(this.product >= MAX_PRODUCT)
? ? ?{
? ? ? ? ?try
? ? ? ? ?{
? ? ? ? ? ? ?wait(); ?
? ? ? ? ? ? ?System.out.println("產(chǎn)品已滿,請稍候再生產(chǎn)");
? ? ? ? ?}
? ? ? ? ?catch(InterruptedException e)
? ? ? ? ?{
? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ?}
? ? ? ? ?return;
? ? ?}
? ? ?this.product++;
? ? ?System.out.println("生產(chǎn)者生產(chǎn)第" + this.product + "個產(chǎn)品.");
? ? ?notifyAll(); ? //通知等待區(qū)的消費者可以取出產(chǎn)品了
?}
?/**
? * 消費者從店員取產(chǎn)品
? */
?public synchronized void consume()
?{
? ? ?if(this.product <= MIN_PRODUCT)
? ? ?{
? ? ? ? ?try
? ? ? ? ?{
? ? ? ? ? ? ?wait();
? ? ? ? ? ? ?System.out.println("缺貨,稍候再取");
? ? ? ? ?}
? ? ? ? ?catch (InterruptedException e)
? ? ? ? ?{
? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ?}
? ? ? ? ?return;
? ? ?}
? ? ?System.out.println("消費者取走了第" + this.product + "個產(chǎn)品.");
? ? ?this.product--;
? ? ?notifyAll(); ? //通知等待去的生產(chǎn)者可以生產(chǎn)產(chǎn)品了
?}
volatile
多線程的內存模型:main memory(主存)、working memory(線程棧),在處理數(shù)據(jù)時,線程會把值從主存load到本地棧,完成操作后再save回去(volatile關鍵詞的作用:每次針對該變量的操作都激發(fā)一次load and save)。

volatile
針對多線程使用的變量如果不是volatile或者final修飾的,很有可能產(chǎn)生不可預知的結果(另一個線程修改了這個值,但是之后在某線程看到的是修改之前的值)。其實道理上講同一實例的同一屬性本身只有一個副本。但是多線程是會緩存值的,本質上,volatile就是不去緩存,直接取值。在線程安全的情況下加volatile會犧牲性能。
太祖長拳:基本線程類
基本線程類指的是Thread類,Runnable接口,Callable接口 我上面已經(jīng)介紹了
九陰真經(jīng):高級多線程控制類
以上都屬于內功心法,接下來是實際項目中常用到的工具了,Java1.5提供了一個非常高效實用的多線程包:java.util.concurrent, 提供了大量高級工具,可以幫助開發(fā)者編寫高效、易維護、結構清晰的Java多線程程序。
1.ThreadLocal類
用處:保存線程的獨立變量。對一個線程類(繼承自Thread) 當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。常用于用戶登錄控制,如記錄session信息。
實現(xiàn):每個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map,功能與map一樣,區(qū)別是桶里放的是entry而不是entry的鏈表。功能還是一個map。)以本身為key,以目標為value。主要方法是get()和set(T a),set之后在map里維護一個threadLocal -> a,get時將a返回。ThreadLocal是一個特殊的容器。
2.原子類(AtomicInteger、AtomicBoolean……)
如果使用atomic wrapper class如atomicInteger,或者使用自己保證原子的操作,則等同于synchronized
//返回值為boolean
AtomicInteger.compareAndSet(int expect,int update)
該方法可用于實現(xiàn)樂觀鎖,考慮文中最初提到的如下場景:a給b付款10元,a扣了10元,b要加10元。此時c給b2元,但是b的加十元代碼約為:
if(b.value.compareAndSet(old, value)){
? return ;
}else{
? //try again
? // if that fails, rollback and log
}
AtomicReference對于AtomicReference 來講,也許對象會出現(xiàn),屬性丟失的情況,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。這時候,AtomicStampedReference就派上用場了。這也是一個很常用的思路,即加上版本號
3.Lock類
lock: 在java.util.concurrent包內。共有三個實現(xiàn):
ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
主要目的是和synchronized一樣, 兩者都是為了解決同步問題,處理資源爭端而產(chǎn)生的技術。功能類似但有一些區(qū)別。
區(qū)別如下:
lock更靈活,可以自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的后解順序)
提供多種加鎖方案,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有trylock的帶超時時間版本。
本質上和監(jiān)視器鎖(即synchronized是一樣的)
能力越大,責任越大,必須控制好加鎖和解鎖,否則會導致災難。
和Condition類的結合。
性能更高
synchronized和Lock性能對比
ReentrantLock 可重入的意義在于持有鎖的線程可以繼續(xù)持有,并且要釋放對等的次數(shù)后才真正釋放該鎖。使用方法是:
1.先new一個實例
static ReentrantLock r=new ReentrantLock();
2.加鎖
r.lock()或r.lockInterruptibly();
此處也是個不同,后者可被打斷。當a線程lock后,b線程阻塞,此時如果是lockInterruptibly,那么在調用b.interrupt()之后,b線程退出阻塞,并放棄對資源的爭搶,進入catch塊。(如果使用后者,必須throw interruptable exception 或catch)
3.釋放鎖
r.unlock()
必須做!何為必須做呢,要放在finally里面。以防止異常跳出了正常流程,導致災難。這里補充一個小知識點,finally是可以信任的:經(jīng)過測試,哪怕是發(fā)生了OutofMemoryError,finally塊中的語句執(zhí)行也能夠得到保證。
ReentrantReadWriteLock
可重入讀寫鎖(讀寫鎖的一個實現(xiàn))
ReentrantReadWriteLock lock = new ReentrantReadWriteLock()
ReadLock r = lock.readLock();
WriteLock w = lock.writeLock();
兩者都有l(wèi)ock,unlock方法。寫寫,寫讀互斥;讀讀不互斥??梢詫崿F(xiàn)并發(fā)讀的高效線程安全代碼
4.容器類
這里就討論比較常用的兩個:
BlockingQueue
ConcurrentHashMap
BlockingQueue阻塞隊列。該類是java.util.concurrent包下的重要類,通過對Queue的學習可以得知,這個queue是單向隊列,可以在隊列頭添加元素和在隊尾刪除或取出元素。類似于一個管 道,特別適用于先進先出策略的一些應用場景。普通的queue接口主要實現(xiàn)有PriorityQueue(優(yōu)先隊列),有興趣可以研究
BlockingQueue在隊列的基礎上添加了多線程協(xié)作的功能:

BlockingQueue
除了傳統(tǒng)的queue功能(表格左邊的兩列)之外,還提供了阻塞接口put和take,帶超時功能的阻塞接口offer和poll。put會在隊列滿的時候阻塞,直到有空間時被喚醒;take在隊 列空的時候阻塞,直到有東西拿的時候才被喚醒。用于生產(chǎn)者-消費者模型尤其好用,堪稱神器。
常見的阻塞隊列有:
ArrayListBlockingQueue
LinkedListBlockingQueue
DelayQueue
SynchronousQueue
ConcurrentHashMap高效的線程安全哈希map。請對比hashTable , concurrentHashMap, HashMap
5.管理類
管理類的概念比較泛,用于管理線程,本身不是多線程的,但提供了一些機制來利用上述的工具做一些封裝。了解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統(tǒng)級管理類 ThreadMXBeanThreadPoolExecutor如果不了解這個類,應該了解前面提到的ExecutorService,開一個自己的線程池非常方便:
ExecutorService e = Executors.newCachedThreadPool();
? ?ExecutorService e = Executors.newSingleThreadExecutor();
? ?ExecutorService e = Executors.newFixedThreadPool(3);
? ?// 第一種是可變大小線程池,按照任務數(shù)來分配線程,
? ?// 第二種是單線程池,相當于FixedThreadPool(1)
? ?// 第三種是固定大小線程池。
? ?// 然后運行
? ?e.execute(new MyRunnableImpl());
該類內部是通過ThreadPoolExecutor實現(xiàn)的,掌握該類有助于理解線程池的管理,本質上,他們都是ThreadPoolExecutor類的各種實現(xiàn)版本。請參見javadoc:

ThreadPoolExecutor參數(shù)解釋
翻譯一下:
corePoolSize:池內線程初始值與最小值,就算是空閑狀態(tài),也會保持該數(shù)量線程。
maximumPoolSize:線程最大值,線程的增長始終不會超過該值。
keepAliveTime:當池內線程數(shù)高于corePoolSize時,經(jīng)過多少時間多余的空閑線程才會被回收?;厥涨疤幱趙ait狀態(tài)
unit:
時間單位,可以使用TimeUnit的實例,如TimeUnit.MILLISECONDS
workQueue:待入任務(Runnable)的等待場所,該參數(shù)主要影響調度策略,如公平與否,是否產(chǎn)生餓死(starving)
threadFactory:線程工廠類,有默認實現(xiàn),如果有自定義的需要則需要自己實現(xiàn)ThreadFactory接口并作為參數(shù)傳入。
同步和死鎖
一個多線程的程序如果是通過 Runnable 接口實現(xiàn)的,則意味著類中的屬性被多個線程共享,那么這樣就會造成一種問題,如果這多個線程要操作同一個資源時就有可能出現(xiàn)資源同步問題。
解決方法:
同步代碼塊
synchronized(同步對象){
?需要同步的代碼
}class MyThread implements Runnable{
? ?privateint ticket = 5 ; ? ?// 假設一共有5張票
? ?public void run(){
? ? ? ?for(int i=0;i<100;i++){
? ? ? ? ? ?synchronized(this){ // 要對當前對象進行同步
? ? ? ? ? ? ? ?if(ticket>0){ ? // 還有票
? ? ? ? ? ? ? ? ? ?try{
? ? ? ? ? ? ? ? ? ? ? ?Thread.sleep(300) ; // 加入延遲
? ? ? ? ? ? ? ? ? ?}catch(InterruptedException e){
? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace() ;
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?System.out.println("賣票:ticket = " + ticket-- );
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ?}
};
publicclass SyncDemo02{
? ?public static void main(String args[]){
? ? ? ?MyThread mt = new MyThread() ; ?// 定義線程對象
? ? ? ?Thread t1 = new Thread(mt) ; ? ?// 定義Thread對象
? ? ? ?Thread t2 = new Thread(mt) ; ? ?// 定義Thread對象
? ? ? ?Thread t3 = new Thread(mt) ; ? ?// 定義Thread對象
? ? ? ?t1.start() ;
? ? ? ?t2.start() ;
? ? ? ?t3.start() ;
? ?}
};
程序執(zhí)行結果:

除了可以將需要的代碼設置成同步代碼塊外,也可以使用 synchronized 關鍵字將一個方法聲明為同步方法。
synchronized 方法返回值 方法名稱(參數(shù)列表){
}
class MyThread implements Runnable{
? ?privateint ticket = 5 ; ? ?// 假設一共有5張票
? ?public void run(){
? ? ? ?for(int i=0;i<100;i++){
? ? ? ? ? ?this.sale() ; ? // 調用同步方法
? ? ? ?}
? ?}
? ?public synchronized void sale(){ ? ?// 聲明同步方法
? ? ? ?if(ticket>0){ ? // 還有票
? ? ? ? ? ?try{
? ? ? ? ? ? ? ?Thread.sleep(300) ; // 加入延遲
? ? ? ? ? ?}catch(InterruptedException e){
? ? ? ? ? ? ? ?e.printStackTrace() ;
? ? ? ? ? ?}
? ? ? ? ? ?System.out.println("賣票:ticket = " + ticket-- );
? ? ? ?}
? ?}
};
publicclass SyncDemo03{
? ?public static void main(String args[]){
? ? ? ?MyThread mt = new MyThread() ; ?// 定義線程對象
? ? ? ?Thread t1 = new Thread(mt) ; ? ?// 定義Thread對象
? ? ? ?Thread t2 = new Thread(mt) ; ? ?// 定義Thread對象
? ? ? ?Thread t3 = new Thread(mt) ; ? ?// 定義Thread對象
? ? ? ?t1.start() ;
? ? ? ?t2.start() ;
? ? ? ?t3.start() ;
? ?}
};
程序執(zhí)行結果:

從程序運行的結果可以發(fā)現(xiàn),此代碼完成了與之前同步代碼同樣的功能。
死鎖
同步可以保證資源共享操作的正確性,但是過多同步也會產(chǎn)生問題。例如,現(xiàn)在張三想要李四的畫,李四想要張三的書,張三對李四說“把你的畫給我,我就給你書”,李四也對張三說“把你的書給我,我就給你畫”兩個人互相等對方先行動,就這么干等沒有結果,這實際上就是死鎖的概念。
所謂死鎖,就是兩個線程都在等待對方先完成,造成程序的停滯,一般程序的死鎖都是在程序運行時出現(xiàn)的。
下面以一個簡單范例說明這個概念
class Zhangsan{ // 定義張三類
? ?public void say(){
? ? ? ?System.out.println("張三對李四說:“你給我畫,我就把書給你?!?#34;) ;
? ?}
? ?public void get(){
? ? ? ?System.out.println("張三得到畫了。") ;
? ?}
};
class Lisi{ // 定義李四類
? ?public void say(){
? ? ? ?System.out.println("李四對張三說:“你給我書,我就把畫給你”") ;
? ?}
? ?public void get(){
? ? ? ?System.out.println("李四得到書了。") ;
? ?}
};
publicclass ThreadDeadLock implements Runnable{
? ?privatestatic Zhangsan zs = new Zhangsan() ; ? ? ? // 實例化static型對象
? ?privatestatic Lisi ls = new Lisi() ; ? ? ? // 實例化static型對象
? ?privateboolean flag = false ; ?// 聲明標志位,判斷那個先說話
? ?public void run(){ ?// 覆寫run()方法
? ? ? ?if(flag){
? ? ? ? ? ?synchronized(zs){ ? // 同步張三
? ? ? ? ? ? ? ?zs.say() ;
? ? ? ? ? ? ? ?try{
? ? ? ? ? ? ? ? ? ?Thread.sleep(500) ;
? ? ? ? ? ? ? ?}catch(InterruptedException e){
? ? ? ? ? ? ? ? ? ?e.printStackTrace() ;
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?synchronized(ls){
? ? ? ? ? ? ? ? ? ?zs.get() ;
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}else{
? ? ? ? ? ?synchronized(ls){
? ? ? ? ? ? ? ?ls.say() ;
? ? ? ? ? ? ? ?try{
? ? ? ? ? ? ? ? ? ?Thread.sleep(500) ;
? ? ? ? ? ? ? ?}catch(InterruptedException e){
? ? ? ? ? ? ? ? ? ?e.printStackTrace() ;
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?synchronized(zs){
? ? ? ? ? ? ? ? ? ?ls.get() ;
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ?}
? ?public static void main(String args[]){
? ? ? ?ThreadDeadLock t1 = new ThreadDeadLock() ; ? ? ?// 控制張三
? ? ? ?ThreadDeadLock t2 = new ThreadDeadLock() ; ? ? ?// 控制李四
? ? ? ?t1.flag = true ;
? ? ? ?t2.flag = false ;
? ? ? ?Thread thA = new Thread(t1) ;
? ? ? ?Thread thB = new Thread(t2) ;
? ? ? ?thA.start() ;
? ? ? ?thB.start() ;
? ?}
};
程序運行結果:

以下代碼不再執(zhí)行,程序進入死鎖狀態(tài)。
總結
1.線程在編寫程序的時候很重要,Java 給多線程編程提供了內置的支持。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務。
2.Java 提供了四種創(chuàng)建線程的方法:
通過實現(xiàn) Runnable 接口;
通過繼承 Thread 類本身;
通過 Callable 和 Future 創(chuàng)建線程。
通過線程池去創(chuàng)建線程
3.知道了并行與并發(fā)
并行:多個cpu實例或者多臺機器同時執(zhí)行一段處理邏輯,是真正的同時。
并發(fā):通過cpu調度算法,讓用戶看上去同時執(zhí)行,實際上從cpu操作層面不是真正的同時。并發(fā)往往在場景中有公用的資源,那么針對這個公用的資源往往產(chǎn)生瓶頸,我們會用TPS或者QPS來反應這個系統(tǒng)的處理能力。
4.線程狀態(tài)
5.鎖的出現(xiàn),同步和死鎖的問題
6.好好學習線程,學好了多線程可以提高程序的性能,線程是一個程序的難點也是一個重點,進階的必備及技能。
