Java多線程入門不完全指南
序言
最近在讀《把時間當作朋友》反璃,序言就教導(dǎo)我“無論是誰,都是在某一刻意識到時間的珍貴假夺,并且注定會因懂事太晚而多少有些后悔”淮蜈。想想我,快走出奔三的泥沼已卷,踏入奔四的深淵梧田,越來越意識到時間的彌足珍貴。于是就想著,趁著還有些精力的時候裁眯,記錄一下所聞鹉梨、所學(xué)、所感穿稳。思考良久存皂,記錄些什么呢?先記錄一下我這拙劣而又不放棄的Java學(xué)習(xí)之路吧,挖坑一篇"粗而廣"的Java多線程介紹壓壓驚逢艘。
基礎(chǔ)知識
線程與進程
- 線程:是操作系統(tǒng)能夠進行運算調(diào)度的最小單位旦袋,是進程中的實際運作單位。一條線程是進程中一個單一順序的控制流它改。
- 進程:是計算機中已運行程序的實體疤孕,是線程的容器。
同步與異步
- 同步:在發(fā)出一個調(diào)用時央拖,在沒有得到結(jié)果之前祭阀,該調(diào)用就不返回。一旦返回鲜戒,必然會得到返回值柬讨。
- 異步:在調(diào)用發(fā)出之后,這個調(diào)用就直接返回袍啡。隨后踩官,被調(diào)用者通過狀態(tài)、通知來通知調(diào)用者境输,或者通過回調(diào)函數(shù)來處理調(diào)用蔗牡。
同步與異步關(guān)注的是消息通信機制。
阻塞與非阻塞
- 阻塞:調(diào)用結(jié)果返回之前嗅剖,當前線程會被掛起辩越。調(diào)用線程只有在得到結(jié)果之后才會返回。
- 非阻塞:調(diào)用在不能立刻得到結(jié)果之前信粮,該調(diào)用不會阻塞當前線程黔攒,當前線程仍會處理其他調(diào)用。
阻塞與非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息强缘,返回值)時的狀態(tài)督惰。
并行與并發(fā)
- 并發(fā):在同一個處理器上“同時”處理多個任務(wù)。通過cpu調(diào)度算法旅掂,讓用戶看上去是同時處理赏胚。
- 并行:在多臺機器上同時處理多個任務(wù),真正的同時商虐。
多線程與線程安全
- 多線程:一個程序?qū)崿F(xiàn)多個線程并發(fā)執(zhí)行觉阅。
- 線程安全:多個線程同時執(zhí)行同一段代碼崖疤,線程的調(diào)度順序不會影響該段代碼的任何結(jié)果。線程安全主要通過線程同步實現(xiàn)典勇。
線程的狀態(tài)
5種狀態(tài)
根據(jù)線程的生命周期劫哼,可以將線程分為以下5種狀態(tài):
- NEW(新建):創(chuàng)建了一個線程,尚未啟動
- RUNNABLE(可運行):線程創(chuàng)建后割笙,其他線程調(diào)用了該線程的
start()
方法沦偎,該線程便等待被CPU調(diào)度執(zhí)行。 - RUNNING(運行):RUNNABLE狀態(tài)的線程被CPU調(diào)度咳蔚,獲取了CPU時間片豪嚎,運行
run()
方法。 - BLOCKERD(阻塞):線程無法獲取CPU時間片谈火,暫時停止運行的狀態(tài)侈询。這個狀態(tài)的線程只有狀態(tài)轉(zhuǎn)為RUNNABLE時,才有機會被CPU調(diào)度執(zhí)行糯耍。阻塞有三種情況:
- 等待阻塞:如RUNNING狀態(tài)的線程執(zhí)行
wait()
方法扔字。 - 同步阻塞:如RUNNING狀態(tài)的線程在獲取同步鎖時,同步鎖被其他線程占用温技。
- 其他阻塞:如RUNNING狀態(tài)的線程執(zhí)行
sleep()
方法或其他線程調(diào)用join()
方法革为。
- 等待阻塞:如RUNNING狀態(tài)的線程執(zhí)行
- DEAD(死亡):線程
run()
方法執(zhí)行結(jié)束或異常退出,該線程死亡舵鳞,結(jié)束生命周期震檩。
狀態(tài)轉(zhuǎn)換
等待隊列和鎖池是如何工作的?
Thread類定義的線程狀態(tài)
-
NEW(新建):線程創(chuàng)建后未啟動蜓堕。
/** * Thread state for a thread which has not yet started. */
-
RUNNABLE(可運行):等待系統(tǒng)資源運行的線程抛虏。
/** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */
-
BLOCKED(阻塞):當一個線程要進入synchronized語句塊/方法時,如果沒有獲取到鎖套才,會變成BLOCKED迂猴。或者在調(diào)用Object.wait()后背伴,被notify()喚醒沸毁,再次進入synchronized語句塊/方法時,如果沒有獲取到鎖傻寂,會變成BLOCKED息尺。進入阻塞狀態(tài)是被動的。
/** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */
-
WAITING(無限等待):等待其它線程執(zhí)行后崎逃,顯示喚醒掷倔。調(diào)用鎖對象的
wait()
方法并未設(shè)置時間眉孩、其它線程調(diào)用join()
方法并未設(shè)置時間个绍、調(diào)用LockSupport.park()
方法都會使當前線程進入此狀態(tài)勒葱。進入等待狀態(tài)是主動的。/** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */
-
TIMED_WAITING(限期等待):等待一段時間后被系統(tǒng)喚醒巴柿,不需要顯示被喚醒凛虽。調(diào)用
Thread.sleep()
方法、調(diào)用鎖對象的wait()
方法并設(shè)置時間广恢、其它線程調(diào)用join()
方法并設(shè)置時間凯旋、調(diào)用LockSupport.parkNanos()
方法、調(diào)用LockSupport.parkUntil()
方法都會使線程進入此狀態(tài)钉迷。/** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */
-
TERMINATED(結(jié)束):線程
run()
方法執(zhí)行結(jié)束或異常退出后的線程狀態(tài)至非。/** * Thread state for a terminated thread. * The thread has completed execution. */
VisualVM線程監(jiān)控
通過JDK自帶的VisualVM工具可以監(jiān)控線程的運行狀態(tài),按照下圖操作可以抓取線程Dump糠聪,監(jiān)控線程的狀態(tài)荒椭。
VisualVM將線程的狀態(tài)分為五種:運行、休眠舰蟆、等待趣惠、駐留、監(jiān)視身害,與Thread類中的線程狀態(tài)對應(yīng)如下:
Thread類 | VisualVM |
---|---|
RUNNABLE | 運行 |
TIMED_WAITING (sleeping) | 休眠 |
TIMED_WAITING (on object monitor) WAITING (on object monitor) | 等待 |
TIMED_WAITING (parking) WAITING (parking) | 駐留 |
BLOCKED (on object monitor) | 監(jiān)視 |
內(nèi)存原子性味悄、可見性和有序性
Java內(nèi)存模型
概念
- 線程之間通信由Java內(nèi)存模型控制,內(nèi)存模型決定了一個線程對共享變量的寫入何時對另一個線程可見塌鸯。
- 每個線程都被抽象出一個保存共享變量副本的工作內(nèi)存侍瑟,線程對共享變量的操作都在此工作內(nèi)存中進行。
-
某個線程無法直接訪問其他線程中的變量丙猬,線程之間的通信需要通過主內(nèi)存來實現(xiàn)丢习。
內(nèi)存模型
線程通信過程
- 線程A從主內(nèi)存中拷貝共享變量1到工作內(nèi)存中的副本,對副本的值進行修改淮悼。
- 線程A刷新修改后的值到主內(nèi)存中咐低。
- 線程B將主內(nèi)存中共享變量1拷貝到工作內(nèi)存中。
并發(fā)編程三個特征
- 原子性:類似于數(shù)據(jù)庫事務(wù)袜腥,要么全部執(zhí)行见擦,要么不執(zhí)行。
如:num++
不具有原子性羹令,先取出num的值鲤屡,再進行加1。
而a=1
,return a
則都具有原子性福侈。 - 可見性:某個線程對共享變量做了修改后酒来,其他線程可以立馬感知到該變量的修改。
- 有序性:若在本線程內(nèi)觀察肪凛,所有操作都是有序的堰汉;若在一個線程中觀察其他線程辽社,所有的操作都是無序的。前半句指“線程內(nèi)表現(xiàn)為串行語義”翘鸭,后半句指“指令重排序”現(xiàn)象和“工作內(nèi)存中主內(nèi)存同步延遲”現(xiàn)象滴铅。
Sychronized與Volatile
- Sychronized:可以保證原子性、可見性和有序性就乓。
Sychronized關(guān)鍵字能保證在同一時刻汉匙,只有一個線程可以獲取鎖執(zhí)行同步代碼,執(zhí)行完之后釋放鎖之前生蚁,會將修改后變量的值刷新的主內(nèi)存中噩翠。 - Volatile:可以保證可見性和有序性,不能保證操作的原子性邦投。
被Volatile關(guān)鍵字修飾的變量绎秒,在寫操作后會加入一條store指令,強行將共享變量最新的值刷新到主內(nèi)存中尼摹。在讀操作前见芹,會加入一條load指令,強行從主內(nèi)存中讀取共享變量最新的值蠢涝。
Java鎖機制
Java中的鎖
- Sychronized
- ReentrantLock
- ReentrantReadWriteLock
這三種鎖是怎么實現(xiàn)的玄呛?什么是AQS?什么是CAS和二?CAS的ABA問題怎么解決徘铝?集群環(huán)境下如何實現(xiàn)同步?有待后續(xù)分曉
Java多線程實現(xiàn)
創(chuàng)建線程的方式
-
繼承Thread類:重寫
run()
方法惯吕,創(chuàng)建線程后調(diào)用start()
方法啟動惕它。public class ThreadOne extends Thread { private static Integer num = 100; @Override public void run() { synchronized (num) { while (num > 0) { try { Thread.sleep((long) (Math.random() * 100)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print("線程:" + this.getName() + "執(zhí)行num--操作后,"); num--; System.out.println("num的值為:" + num); } } } public static void main(String[] args) { Thread t1 = new ThreadOne(); Thread t2 = new ThreadOne(); Thread t3 = new ThreadOne(); t1.start(); t2.start(); t3.start(); } }
-
實現(xiàn)Runnable接口:實現(xiàn)
run()
方法废登,創(chuàng)建線程后調(diào)用start()
方法啟動淹魄。public class ThreadTwo implements Runnable{ private static Integer num = 100; @Override public void run() { synchronized (num) { while (num > 0) { try { Thread.sleep((long) (Math.random() * 100)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print("線程:" + Thread.currentThread().getName() + "執(zhí)行num--操作后,"); num--; System.out.println("num的值為:" + num); } } } public static void main(String[] args) { Thread t1 = new Thread(new ThreadTwo()); Thread t2 = new Thread(new ThreadTwo()); Thread t3 = new Thread(new ThreadTwo()); t1.start(); t2.start(); t3.start(); } }
-
實現(xiàn)Callable接口:實現(xiàn)
call()
方法堡距,可以創(chuàng)建有返回值的線程甲锡。public class ThreadThree implements Callable<Integer> { private static Integer num = 100; @Override public Integer call() throws Exception { synchronized (num) { while (num > 0) { try { Thread.sleep((long) (Math.random() * 100)); } catch (InterruptedException e) { e.printStackTrace(); } num--; } } return num; } public static void main(String[] args) throws InterruptedException, ExecutionException { FutureTask<Integer> task1 = new FutureTask<>(new ThreadThree()); FutureTask<Integer> task2 = new FutureTask<>(new ThreadThree()); FutureTask<Integer> task3 = new FutureTask<>(new ThreadThree()); Thread t1 = new Thread(task1); Thread t2 = new Thread(task2); Thread t3 = new Thread(task3); t1.start(); t2.start(); t3.start(); System.out.println("線程1的返回值:" + task1.get()); System.out.println("線程2的返回值:" + task2.get()); System.out.println("線程3的返回值:" + task3.get()); } }
Executor線程池框架
為什么要用線程池?
- 通過復(fù)用“池”中的已有線程羽戒,減少線程創(chuàng)建和銷毀的開銷缤沦,提高性能。
- 可以設(shè)置最大并發(fā)線程數(shù)易稠,避免過多線程競爭資源缸废。
Executors創(chuàng)建線程池
-
newFixedThreadPool創(chuàng)建固定線程數(shù)的線程池。若所有線程都處于活動狀態(tài),新提交的任務(wù)會在隊列中等待企量。若某個線程異常結(jié)束测萎,則線程池會重新補充一個新線程。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
-
newCachedThreadPool創(chuàng)建可緩存的線程池梁钾,若線程池中的線程超過60s未被使用會被移除绳泉。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
-
newScheduledThreadPool創(chuàng)建定時線程逊抡,可定時執(zhí)行任務(wù)姆泻。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
-
newSingleThreadExecutor創(chuàng)建一個單線程來執(zhí)行任務(wù)。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
線程池執(zhí)行任務(wù)
-
使用
execute()
方法執(zhí)行Runnable任務(wù)public class RunnableThreadPool { public static void main(String[] args) { ExecutorService eService = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { eService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在執(zhí)行"); } }); } eService.shutdown(); } }
-
使用
submit()
方法執(zhí)行Callable任務(wù)public class CallableThreadPool { public static void main(String[] args) { ExecutorService eService = Executors.newFixedThreadPool(5); List<Future<String>> resultList = new ArrayList<Future<String>>(); for (int i = 0; i < 10; i++) { Future<String> future = eService.submit(new Callable<String>() { @Override public String call() throws Exception { return Thread.currentThread().getName() + "已執(zhí)行call方法并成功返回"; } }); resultList.add(future); } eService.shutdown(); for (Future<String> future : resultList) { //等待future返回結(jié)果 while(!future.isDone()); try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
那么線程池的實現(xiàn)原理是什么冒嫡?有待后續(xù)見分曉