摘要:上下文切換會(huì)影響到線(xiàn)程的執(zhí)行速度,對(duì)于系統(tǒng)來(lái)說(shuō)意味著會(huì)消耗大量的時(shí)間減少上下文切換的方式無(wú)鎖并發(fā)編程,在多線(xiàn)程競(jìng)爭(zhēng)鎖時(shí),會(huì)導(dǎo)致大量的上下文切換。線(xiàn)程在中的使用在中實(shí)現(xiàn)多線(xiàn)程的方式比較簡(jiǎn)單,因?yàn)橹刑峁┝朔浅7奖愕膩?lái)實(shí)現(xiàn)多線(xiàn)程。
文章簡(jiǎn)介
上一篇文章我們了解了進(jìn)程和線(xiàn)程的發(fā)展歷史、線(xiàn)程的生命周期、線(xiàn)程的優(yōu)勢(shì)和使用場(chǎng)景,這一篇,我們從Java層面更進(jìn)一步了解線(xiàn)程的使用
內(nèi)容導(dǎo)航并發(fā)編程的挑戰(zhàn)
線(xiàn)程在Java中的使用
并發(fā)編程的挑戰(zhàn)引入多線(xiàn)程的目的在第一篇提到過(guò),就是為了充分利用CPU是的程序運(yùn)行得更快,當(dāng)然并不是說(shuō)啟動(dòng)的線(xiàn)程越多越好。在實(shí)際使用多線(xiàn)程的時(shí)候,會(huì)面臨非常多的挑戰(zhàn)
線(xiàn)程安全問(wèn)題線(xiàn)程安全問(wèn)題值的是當(dāng)多個(gè)線(xiàn)程訪(fǎng)問(wèn)同一個(gè)對(duì)象時(shí),如果不考慮這些運(yùn)行時(shí)環(huán)境采用的調(diào)度方式或者這些線(xiàn)程將如何交替執(zhí)行,并且在代碼中不需要任何同步操作的情況下,這個(gè)類(lèi)都能夠表現(xiàn)出正確的行為,那么這個(gè)類(lèi)就是線(xiàn)程安全的
比如下面的代碼是一個(gè)單例模式,在代碼的注釋出,如果多個(gè)線(xiàn)程并發(fā)訪(fǎng)問(wèn),則會(huì)出現(xiàn)多個(gè)實(shí)例。導(dǎo)致無(wú)法實(shí)現(xiàn)單例的效果
public class SingletonDemo { private static SingletonDemo singletonDemo=null; private SingletonDemo(){} public static SingletonDemo getInstance(){ if(singletonDemo==null){/***線(xiàn)程安全問(wèn)題***/ singletonDemo=new SingletonDemo(); } return singletonDemo; } }
通常來(lái)說(shuō),我們把多線(xiàn)程編程中的線(xiàn)程安全問(wèn)題歸類(lèi)成如下三個(gè),至于每一個(gè)問(wèn)題的本質(zhì),在后續(xù)的文章中我們會(huì)多帶帶講解
原子性
可見(jiàn)性
有序性
上下文切換問(wèn)題在單核心CPU架構(gòu)中,對(duì)于多線(xiàn)程的運(yùn)行是基于CPU時(shí)間片切換來(lái)實(shí)現(xiàn)的偽并行。由于時(shí)間片非常短導(dǎo)致用戶(hù)以為是多個(gè)線(xiàn)程并行執(zhí)行。而一次上下文切換,實(shí)際就是當(dāng)前線(xiàn)程執(zhí)行一個(gè)時(shí)間片之后切換到另外一個(gè)線(xiàn)程,并且保存當(dāng)前線(xiàn)程執(zhí)行的狀態(tài)這個(gè)過(guò)程。上下文切換會(huì)影響到線(xiàn)程的執(zhí)行速度,對(duì)于系統(tǒng)來(lái)說(shuō)意味著會(huì)消耗大量的CPU時(shí)間
減少上下文切換的方式
無(wú)鎖并發(fā)編程,在多線(xiàn)程競(jìng)爭(zhēng)鎖時(shí),會(huì)導(dǎo)致大量的上下文切換。避免使用鎖去解決并發(fā)問(wèn)題可以減少上下文切換
CAS算法,CAS是一種樂(lè)觀(guān)鎖機(jī)制,不需要加鎖
使用與硬件資源匹配合適的線(xiàn)程數(shù)
死鎖在解決線(xiàn)程安全問(wèn)題的場(chǎng)景中,我們會(huì)比較多的考慮使用鎖,因?yàn)樗褂帽容^簡(jiǎn)單。但是鎖的使用如果不恰當(dāng),則會(huì)引發(fā)死鎖的可能性,一旦產(chǎn)生死鎖,就會(huì)造成比較嚴(yán)重的問(wèn)題:產(chǎn)生死鎖的線(xiàn)程會(huì)一直占用鎖資源,導(dǎo)致其他嘗試獲取鎖的線(xiàn)程也發(fā)生死鎖,造成系統(tǒng)崩潰
以下是死鎖的簡(jiǎn)單案例
public class DeadLockDemo { //定義鎖對(duì)象 private final Object lockA = new Object(); private final Object lockB = new Object(); private void deadLock(){ new Thread(()->{ synchronized (lockA){ try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println("Lock B"); } } }).start(); new Thread(()->{ synchronized (lockB){ synchronized (lockA){ System.out.println("Lock A"); } } }).start(); } public static void main(String[] args) { new DeadLockDemo().deadLock(); } }通過(guò)jstack分析死鎖
1.首先通過(guò)jps獲取當(dāng)前運(yùn)行的進(jìn)程的pid
6628 Jps 17588 RemoteMavenServer 19220 Launcher 19004 DeadLockDemo
2.jstack打印堆棧信息,輸入 jstack19004, 會(huì)打印如下日志,可以很明顯看到死鎖的信息提示
Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x000000001d461e68 (object 0x000000076b310df8, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000001d463258 (object 0x000000076b310e08, a java.lang.Object), which is held by "Thread-1"
解決死鎖的手段資源限制
1.保證多個(gè)線(xiàn)程按照相同的順序獲取鎖
2.設(shè)置獲取鎖的超時(shí)時(shí)間,超過(guò)設(shè)定時(shí)間以后自動(dòng)釋放
3.死鎖檢測(cè)
資源限制主要指的是硬件資源和軟件資源,在開(kāi)發(fā)多線(xiàn)程應(yīng)用時(shí),程序的執(zhí)行速度受限于這兩個(gè)資源。硬件的資源限制無(wú)非就是磁盤(pán)、CPU、內(nèi)存、網(wǎng)絡(luò);軟件資源的限制有很多,比如數(shù)據(jù)庫(kù)連接數(shù)、計(jì)算機(jī)能夠支持的最大連接數(shù)等
資源限制導(dǎo)致的問(wèn)題最直觀(guān)的體現(xiàn)就是前面說(shuō)的上下文切換,也就是CPU資源和線(xiàn)程資源的嚴(yán)重不均衡導(dǎo)致頻繁上下文切換,反而會(huì)造成程序的運(yùn)行速度下降
資源限制的主要解決方案,就是缺啥補(bǔ)啥。CPU不夠用,可以增加CPU核心數(shù);一臺(tái)機(jī)器的資源有限,則增加多臺(tái)機(jī)器來(lái)做集群。線(xiàn)程在Java中的使用
在Java中實(shí)現(xiàn)多線(xiàn)程的方式比較簡(jiǎn)單,因?yàn)镴ava中提供了非常方便的API來(lái)實(shí)現(xiàn)多線(xiàn)程。
1.繼承Thread類(lèi)實(shí)現(xiàn)多線(xiàn)程
2.實(shí)現(xiàn)Runnable接口
3.實(shí)現(xiàn)Callable接口通過(guò)Future包裝器來(lái)創(chuàng)建Thread線(xiàn)程,這種是帶返回值的線(xiàn)程
4.使用線(xiàn)程池ExecutorService
繼承Thread類(lèi),然后重寫(xiě)run方法,在run方法中編寫(xiě)當(dāng)前線(xiàn)程需要執(zhí)行的邏輯。最后通過(guò)線(xiàn)程實(shí)例的start方法來(lái)啟動(dòng)一個(gè)線(xiàn)程
public class ThreadDemo extends Thread{ @Override public void run() { //重寫(xiě)run方法,提供當(dāng)前線(xiàn)程執(zhí)行的邏輯 System.out.println("Hello world"); } public static void main(String[] args) { ThreadDemo threadDemo=new ThreadDemo(); threadDemo.start(); } }
Thread類(lèi)其實(shí)是實(shí)現(xiàn)了Runnable接口,因此Thread自己也是一個(gè)線(xiàn)程實(shí)例,但是我們不能直接用 newThread().start()去啟動(dòng)一個(gè)線(xiàn)程,原因很簡(jiǎn)單,Thread類(lèi)中的run方法是沒(méi)有實(shí)際意義的,只是一個(gè)調(diào)用通過(guò)構(gòu)造函數(shù)傳遞寄來(lái)的另一個(gè)Runnable實(shí)現(xiàn)類(lèi)的run方法,這塊的具體演示會(huì)在Runnable接口的代碼中看到
public class Thread implements Runnable { /* What will be run. */ private Runnable target; ... @Override public void run() { if (target != null) { target.run(); } } ...實(shí)現(xiàn)Runnable接口
如果需要使用線(xiàn)程的類(lèi)已經(jīng)繼承了其他的類(lèi),那么按照J(rèn)ava的單一繼承原則,無(wú)法再繼承Thread類(lèi)來(lái)實(shí)現(xiàn)線(xiàn)程,所以可以通過(guò)實(shí)現(xiàn)Runnable接口來(lái)實(shí)現(xiàn)多線(xiàn)程
public class RunnableDemo implements Runnable{ @Override public void run() { //重寫(xiě)run方法,提供當(dāng)前線(xiàn)程執(zhí)行的邏輯 System.out.println("Hello world"); } public static void main(String[] args) { RunnableDemo runnableDemo=new RunnableDemo(); new Thread(runnableDemo).start(); } }
上面的代碼中,實(shí)現(xiàn)了Runnable接口,重寫(xiě)了run方法;接著為了能夠啟動(dòng)RunnableDemo這個(gè)線(xiàn)程,必須要實(shí)例化一個(gè)Thread類(lèi),通過(guò)構(gòu)造方法傳遞一個(gè)Runnable接口實(shí)現(xiàn)類(lèi)去啟動(dòng),Thread的run方法就會(huì)調(diào)用target.run來(lái)運(yùn)行當(dāng)前線(xiàn)程,代碼在上面.實(shí)現(xiàn)Callable接口
在有些多線(xiàn)程使用的場(chǎng)景中,我們有時(shí)候需要獲取異步線(xiàn)程執(zhí)行完畢以后的反饋結(jié)果,也許是主線(xiàn)程需要拿到子線(xiàn)程的執(zhí)行結(jié)果來(lái)處理其他業(yè)務(wù)邏輯,也許是需要知道線(xiàn)程執(zhí)行的狀態(tài)。那么Callable接口可以很好的實(shí)現(xiàn)這個(gè)功能
public class CallableDemo implements Callable{ @Override public String call() throws Exception { return "hello world"; } public static void main(String[] args) throws ExecutionException, InterruptedException { Callable callable=new CallableDemo(); FutureTask task=new FutureTask<>(callable); new Thread(task).start(); System.out.println(task.get());//獲取線(xiàn)程的返回值 } }
在上面代碼案例中的最后一行 task.get()就是獲取線(xiàn)程的返回值,這個(gè)過(guò)程是阻塞的,當(dāng)子線(xiàn)程還沒(méi)有執(zhí)行完的時(shí)候,主線(xiàn)程會(huì)一直阻塞直到結(jié)果返回使用線(xiàn)程池
為了減少頻繁創(chuàng)建線(xiàn)程和銷(xiāo)毀線(xiàn)程帶來(lái)的性能開(kāi)銷(xiāo),在實(shí)際使用的時(shí)候我們會(huì)采用線(xiàn)程池來(lái)創(chuàng)建線(xiàn)程,在這里我不打算展開(kāi)多線(xiàn)程的好處和原理,我會(huì)在后續(xù)的文章中多帶帶說(shuō)明。
public class ExecutorServiceDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { //創(chuàng)建一個(gè)固定線(xiàn)程數(shù)的線(xiàn)程池 ExecutorService pool = Executors.newFixedThreadPool(1); Future future=pool.submit(new CallableDemo()); System.out.println(future.get()); } }
pool.submit有幾個(gè)重載方法,可以傳遞帶返回值的線(xiàn)程實(shí)例,也可以傳遞不帶返回值的線(xiàn)程實(shí)例,源代碼如下
/*01*/Future> submit(Runnable task); /*02*/Future submit(Runnable task, T result); /*03*/ Future submit(Callable task);
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72577.html
摘要:概述本系列文章將從開(kāi)發(fā)者角度梳理開(kāi)發(fā)實(shí)時(shí)聯(lián)網(wǎng)游戲后臺(tái)服務(wù)過(guò)程中可能面臨的挑戰(zhàn),并針對(duì)性地提供相應(yīng)解決思路,期望幫助開(kāi)發(fā)者依據(jù)自身游戲特點(diǎn)做出合理的技術(shù)選型。多路復(fù)用避免了讀寫(xiě)阻塞,減少了上下文切換,提升了利用率和系統(tǒng)吞吐率。 概述:本系列文章將從開(kāi)發(fā)者角度梳理開(kāi)發(fā)實(shí)時(shí)聯(lián)網(wǎng)游戲后臺(tái)服務(wù)過(guò)程中可能面臨的挑戰(zhàn),并針對(duì)性地提供相應(yīng)解決思路,期望幫助開(kāi)發(fā)者依據(jù)自身游戲特點(diǎn)做出合理的技術(shù)選型。 關(guān)...
摘要:年月日,平安科技數(shù)據(jù)庫(kù)產(chǎn)品資深工程師何志勇在第十屆數(shù)據(jù)庫(kù)技術(shù)大會(huì)上分享了在平安核心系統(tǒng)的引入及應(yīng)用,通過(guò)對(duì)進(jìn)行測(cè)試,詳細(xì)解析如何選擇適用于金融行業(yè)級(jí)別的開(kāi)源分布式數(shù)據(jù)庫(kù),以及平安財(cái)神節(jié)活動(dòng)中引入的全流程應(yīng)用實(shí)踐案例分享。 作者:何志勇本文轉(zhuǎn)載自公眾號(hào)「平安科技數(shù)據(jù)庫(kù)產(chǎn)品團(tuán)隊(duì)」。 2019 年 5 月 9 日,平安科技數(shù)據(jù)庫(kù)產(chǎn)品資深工程師何志勇在第十屆數(shù)據(jù)庫(kù)技術(shù)大會(huì) DTCC 上分享了《...
閱讀 635·2023-04-25 18:37
閱讀 2796·2021-10-12 10:12
閱讀 8376·2021-09-22 15:07
閱讀 577·2019-08-30 15:55
閱讀 3183·2019-08-30 15:44
閱讀 2204·2019-08-30 15:44
閱讀 1635·2019-08-30 13:03
閱讀 1570·2019-08-30 12:55