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