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