Java并發(fā)編程基礎線程(java并發(fā)線程代碼)

      網友投稿 805 2025-03-31

      簡介:

      線程是操作系統(tǒng)調度的最小單元,在多核環(huán)境中,多個線程能同時執(zhí)行,如果運用得當,能顯著的提升程序的性能。

      一、線程初步認識

      1、什么是線程

      操作系統(tǒng)運行一個程序會為其啟動一個進程。例如,啟動一個Java程序會創(chuàng)建一個Java進程。現(xiàn)代操作系統(tǒng)調度的最小單元是線程,線程也稱為輕量級進程(Light Weight Process),一個進程中可以創(chuàng)建一個到多個線程,線程擁有自己的計數(shù)器、堆棧和局部變量等屬性,并且能訪問共享的內存變量。處理器會通過快速切換這些線程,來執(zhí)行程序。

      2、Java本身就是多線程

      示例代碼:

      package com.lizba.p2; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.Arrays; /** *

      * *

      * * @Author: Liziba * @Date: 2021/6/13 23:03 */ public class MultiThread { public static void main(String[] args) { // 獲取Java線程管理MXBean ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); // 獲取線程和線程堆棧信息; // boolean lockedMonitors = false 不需要獲取同步的monitor信息; // boolean lockedSynchronizers = false 不需要獲取同步的synchronizer信息 ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); // 打印線程ID和線程name Arrays.stream(threadInfos).forEach(threadInfo -> { System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName()); }); } }

      輸出結果(不一定一致):

      [6]Monitor Ctrl-Break // idea中特有的線程(不用管)

      [5]Attach Listener // JVM進程間的通信線程

      [4]Signal Dispatcher // 分發(fā)處理發(fā)送給JVM信號的線程

      [3]Finalizer // 調用對象的finalizer線程

      [2]Reference Handler // 清楚Reference的線程

      [1]main // main線程,用戶程序入口

      總結:

      從輸出結果不難看出,Java程序本身就是多線程的。它不僅僅只有一個main線程在運行,而是main線程和其他多個線程在同時運行。

      3、為什么要使用多線程

      使用多線程的好處如下:

      更多處理器核心

      計算機處理器核心數(shù)增多,由以前的高主頻向多核心技術發(fā)展,現(xiàn)在的計算機更擅長于并行計算,因此如何充分利用多核心處理器是現(xiàn)在的主要問題。線程是操作系統(tǒng)調度的最小單元,一個程序作為一個進程來運行,它會創(chuàng)建多個線程,而一個線程在同一時刻只能運行在一個處理器上。因此一個進程如果能使用多線程計算,將其計算邏輯分配到多個處理器核心上,那么相比單線程運行將會有更顯著的性能提升。

      更快響應時間

      在復雜業(yè)務場景中,我們可以將非強一致性關聯(lián)的業(yè)務派發(fā)給其他線程處理(或者使用消息隊列)。這樣可以減少應用響應用戶請求的時間

      更好的編程模型

      合理使用Java的提供的多線程編程模型,能使得程序員更好的解決問題,而不需要過于復雜的考慮如何將其多線程化。

      4、線程的優(yōu)先級

      現(xiàn)代操作系統(tǒng)基本采用的是時間片分配的方式來調度線程,也就是操作系統(tǒng)將CPU的運行分為一個個時間片,線程會分配的若干時間片,當線程時間片用完了,就會發(fā)生線程調度等待下次時間片的分配。線程在一次CPU調度中能執(zhí)行多久,取決于所分時間片的多少,而線程優(yōu)先級就是決定線程需要多或者少分配一些處理器資源的線程屬性。

      在Java線程中,線程的優(yōu)先級的可設置范圍是1-10,默認優(yōu)先級是5,理論上優(yōu)先級高的線程分配時間片數(shù)量要優(yōu)先于低的線程(部分操作系統(tǒng)這個設置是不生效的);

      示例代碼:

      package com.lizba.p2; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** *

      * 線程優(yōu)先級設置 *

      * * @Author: Liziba * @Date: 2021/6/14 12:03 */ public class Priority { /** 線程執(zhí)行流程控制開關 */ private static volatile boolean notStart = true; /** 線程執(zhí)行流程控制開關 */ private static volatile boolean notEnd = true; public static void main(String[] args) throws InterruptedException { List jobs = new ArrayList<>(); // 設置5個優(yōu)先級為1的線程,設置5個優(yōu)先級為10的線程 for (int i = 0; i < 10; i++) { int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY; Job job = new Job(priority); jobs.add(job); Thread thread = new Thread(job, "Thread:" + i); thread.setPriority(priority); thread.start(); } notStart = false; TimeUnit.SECONDS.sleep(10); notEnd = false; jobs.forEach( job -> System.out.println("Job priority : " + job.priority + ", Count : " + job.jobCount) ); } /** * 通過Job來記錄線程的執(zhí)行次數(shù)和優(yōu)先級 */ static class Job implements Runnable { private int priority; private long jobCount; public Job(int priority) { this.priority = priority; } @Override public void run() { while (notStart) { // 讓出CPU時間片,等待下次調度 Thread.yield(); } while (notEnd) { // 讓出CPU時間片,等待下次調度 Thread.yield(); jobCount++; } } } }

      執(zhí)行結果:

      從輸出結果上來看,優(yōu)先級為1的線程和優(yōu)先級為10的線程執(zhí)行的次數(shù)非常相近,因此這表明程序正確性是不能依賴線程的優(yōu)先級高低的。

      5、線程的狀態(tài)

      線程的生命周期如下:

      狀態(tài)名稱

      說明

      NEW

      初始狀態(tài),線程被構建,并未調用start()方法

      RUNNABLE

      運行狀態(tài),Java線程將操作系統(tǒng)中的就緒和運行兩種狀態(tài)統(tǒng)稱為“運行中”

      BLOCKED

      阻塞狀態(tài),線程阻塞于鎖

      WAITING

      等待狀態(tài),線程進入等待狀態(tài),進入該狀態(tài)表示當前線程需要等待其他線程作出一些特定動作(通知或中斷)

      TIME_WAITING

      超時等待,先比WAITING可以在指定的時間內自行返回

      TERMINATED

      終止狀態(tài),表示當前線程已經執(zhí)行完畢

      通過代碼來查看Java線程的狀態(tài)

      代碼示例:

      package com.lizba.p2; import java.util.concurrent.TimeUnit; /** *

      * 睡眠指定時間工工具類 *

      * * @Author: Liziba * @Date: 2021/6/14 13:27 */ public class SleepUtil { public static final void sleepSecond(long seconds) { try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); } } }

      package com.lizba.p2; /** *

      * 線程狀態(tài)示例代碼 *

      * * @Author: Liziba * @Date: 2021/6/14 13:25 */ public class ThreadStateDemo { public static void main(String[] args) { // TimeWaiting new Thread(new TimeWaiting(), "TimeWaitingThread").start(); // Waiting new Thread(new Waiting(), "WaitingThread").start(); // Blocked1和Blocked2一個獲取鎖成功,一個獲取失敗 new Thread(new Blocked(), "Blocked1Thread").start(); new Thread(new Blocked(), "Blocked2Thread").start(); } // 線程不斷的進行睡眠 static class TimeWaiting implements Runnable { @Override public void run() { while (true) { SleepUtil.sleepSecond(100); } } } // 線程等待在Waiting.class實例上 static class Waiting implements Runnable { @Override public void run() { while (true) { synchronized (Waiting.class) { try { Waiting.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } // 該線程Blocked.class實例上加鎖,不會釋放該鎖 static class Blocked implements Runnable { @Override public void run() { synchronized (Blocked.class) { while (true) { SleepUtil.sleepSecond(100); } } } } }

      使用JPS查看Java進程:

      查看示例代碼ThreadStateDemo進程ID是2576,鍵入jstack 2576查看輸出:

      整理輸出結果:

      線程名稱

      線程狀態(tài)

      Blocked2Thread

      BLOCKED (on object monitor),阻塞在獲取Blocked.class的鎖上

      Blocked1Thread

      TIMED_WAITING (sleeping)

      WaitingThread

      WAITING (on object monitor)

      TimeWaitingThread

      TIMED_WAITING (sleeping)

      總結:

      線程在自身生命周期中不是規(guī)定處于某一個狀態(tài),而是隨著代碼的執(zhí)行在不同的狀態(tài)之間進行切換。

      Java線程的狀態(tài)變化圖如下:

      Java線程狀態(tài)變遷圖

      總結:

      線程創(chuàng)建后,調用start()方法開始運行

      線程執(zhí)行wait()方法后,線程進入等待狀態(tài),進入等待的線程需要依靠其他線程才能夠返回到運行狀態(tài)

      超時等待相當于在等待朱姑娘太的基礎上增加了超時限制,達到設置的超時時間后返回到運行狀態(tài)

      線程執(zhí)行同步方法或代碼塊時,未獲取到鎖的線程,將會進入到阻塞狀態(tài)。

      線程執(zhí)行完Runnable的run()方法之后進入到終止狀態(tài)

      阻塞在Java的concurrent包中Lock接口的線程是等待狀態(tài),因為Lock接口阻塞的實現(xiàn)使用的是Daemon線程

      6、Daemon線程

      簡介:

      Daemon線程是一種支持型線程,它的主要作用是程序中后臺調度和支持性工作。當一個Java虛擬機中不存在非Daemon線程的時候,Java虛擬機將會退出。Daemon線程需要在啟動之前設置,不能在啟動之后設置。

      設置方式:

      Java并發(fā)編程基礎之線程(java并發(fā)線程代碼)

      Thread.setDaemon(true)

      需要特別注意的點:

      Daemon線程被用作支持性工作的完成,但是在Java虛擬機退出時Daemon線程的finally代碼塊不一定執(zhí)行。

      示例代碼:

      package com.lizba.p2; /** *

      * DaemonRunner線程 *

      * * @Author: Liziba * @Date: 2021/6/14 19:50 */ public class DaemonRunner implements Runnable{ @Override public void run() { try { SleepUtil.sleepSecond(100); } finally { System.out.println("DaemonRunner finally run ..."); } } }

      測試:

      package com.lizba.p2; /** *

      * *

      * * @Author: Liziba * @Date: 2021/6/14 19:59 */ public class DaemonTest { public static void main(String[] args) { Thread t = new Thread(new DaemonRunner(), "DaemonRunner"); t.setDaemon(true); t.start(); } }

      輸出結果:

      總結:

      不難發(fā)現(xiàn),DaemonRunner的run方法的finally代碼塊并沒有執(zhí)行,這是因為,當Java虛擬機種已經沒有非Daemon線程時,虛擬機會立即退出,虛擬機中的所以daemon線程需要立即終止,所以線程DaemonRunner會被立即終止,finally并未執(zhí)行。

      二、線程啟動和終止

      1、構造線程

      運行線程之前需要構造一個線程對象,線程對象在構造的時候需要設置一些線程的屬性,這些屬性包括線程組、線程的優(yōu)先級、是否時daemon線程、線程名稱等信息。

      代碼示例:

      來自java.lang.Thread

      /** * Initializes a Thread. * * @param g the Thread group * @param target the object whose run() method gets called * @param name the name of the new Thread * @param stackSize the desired stack size for the new thread, or * zero to indicate that this parameter is to be ignored. * @param acc the AccessControlContext to inherit, or * AccessController.getContext() if null * @param inheritThreadLocals if {@code true}, inherit initial values for * inheritable thread-locals from the constructing thread */ private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } // 設置線程名稱 this.name = name; // 當前線程設置為該線程的父線程 Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { if (security != null) { g = security.getThreadGroup(); } if (g == null) { g = parent.getThreadGroup(); } } g.checkAccess(); if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); // 設置線程組 this.group = g; // 將daemon屬性設置為父線程的對應的屬性 this.daemon = parent.isDaemon(); // 將prority屬性設置為父線程的對應的屬性 this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); // 復制父線程的InheritableThreadLocals屬性 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; // 設置一個線程id tid = nextThreadID(); }

      總結:

      在上述代碼中,一個新構建的線程對象時由其parent線程來分配空間的,而child繼承了parent是否為Daemon、優(yōu)先級和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時會分配一個唯一的ID來標志線程。此時一個完整的能夠運行的線程對象就初始化好了,在堆內存中等待運行。

      2、什么是線程中斷

      中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作。線程通過檢查自身是否被中斷來進行響應,線程通過方法isInterrupted()來進行判斷是否被中斷,也可以通過調用靜態(tài)方法Thread.interrupted()對當前線程的中斷標志位進行復位。

      如下情況不能準確判斷線程是否被中斷過:

      線程已經終止運行,即使被中斷過,isInterrupted()方法也會返回false

      方法拋出InterruptedException異常,即使被中斷過,調用isInterrupted()方法將會返回false,這是因為拋出InterruptedException之前會清除中斷標志。

      示例代碼:

      package com.lizba.p2; /** *

      * 線程中斷示例代碼 *

      * * @Author: Liziba * @Date: 2021/6/14 20:36 */ public class Interrupted { public static void main(String[] args) { // sleepThread不停的嘗試睡眠 Thread sleepThread = new Thread(new SleepRunner(), "sleepThread"); sleepThread.setDaemon(true); // busyThread Thread busyThread = new Thread(new BusyRunner(), "busyThread"); busyThread.setDaemon(true); // 啟動兩個線程 sleepThread.start(); busyThread.start(); // 休眠5秒,讓sleepThread和busyThread運行充分 SleepUtil.sleepSecond(5); // 中斷兩個線程 sleepThread.interrupt(); busyThread.interrupt(); System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted()); System.out.println("BusyThread interrupted is " + busyThread.isInterrupted()); // 睡眠主線程,防止daemon線程退出 SleepUtil.sleepSecond(2); } static class SleepRunner implements Runnable { @Override public void run() { while (true) { SleepUtil.sleepSecond(10); } } } static class BusyRunner implements Runnable { @Override public void run() { while (true) {} } } }

      查看運行結果:

      總結:

      拋出InterruptedException的是sleepThread線程,雖然兩者都被中斷過,但是sleepThread線程的中斷標志返回的是false,這是因為TimeUnit.SECONDS.sleep(seconds)會拋出InterruptedException異常,拋出異常之前,sleepThread線程的中斷標志被清除了。但是,busyThread一直在運行沒有拋出異常,中斷位沒有被清除。

      3、suspend()、resume()和stop()

      舉例:

      線程這三個方法,相當于QQ音樂播放音樂時的暫停、恢復和停止操作。(注意這些方法已經過期了,不建議使用。)

      示例代碼:

      package com.lizba.p2; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; /** *

      * 線程過期方法示例 *

      * * @Author: Liziba * @Date: 2021/6/14 20:57 */ public class Deprecated { static DateFormat format = new SimpleDateFormat("HH:mm:ss"); public static void main(String[] args) { Thread printThread = new Thread(new PrintThread(), "PrintThread"); printThread.start(); SleepUtil.sleepSecond(3); // 暫停printThread輸出 printThread.suspend(); System.out.println("main suspend PrintThread at " + format.format(new Date())); SleepUtil.sleepSecond(3); // 恢復printThread輸出 printThread.resume(); System.out.println("main resume PrintThread at " + format.format(new Date())); SleepUtil.sleepSecond(3); // 終止printThread輸出 printThread.stop(); System.out.println("main stop PrintThread at " + format.format(new Date())); SleepUtil.sleepSecond(3); } static class PrintThread implements Runnable { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + "Run at " + format.format(new Date())); SleepUtil.sleepSecond(1); } } } }

      輸出結果:

      總結:

      上述代碼執(zhí)行輸出的結果,與API說明和我們的預期完成一致,但是看似正確的代碼卻隱藏這很多問題。

      存在問題:

      suspend()方法調用后不會釋放已占有的資源(比如鎖),可能會導致死鎖

      stop()方法在終結一個線程時不能保證資源的正常釋放,可能會導致程序處于不確定的工作狀態(tài)

      4、正確的終止線程

      調用線程的interrupt()方法

      使用一個Boolean類型的變量來控制是否停止任務并終止線程

      示例代碼:

      package com.lizba.p2; /** *

      * 標志位終止線程示例代碼 *

      * * @Author: Liziba * @Date: 2021/6/14 21:17 */ public class ShutDown { public static void main(String[] args) { Runner one = new Runner(); Thread t = new Thread(one, "CountThread"); t.start(); SleepUtil.sleepSecond(1); t.interrupt(); Runner two = new Runner(); t = new Thread(two, "CountThread"); t.start(); SleepUtil.sleepSecond(1); two.cancel(); } private static class Runner implements Runnable { private long i; private volatile boolean on = true; @Override public void run() { while (on && !Thread.currentThread().isInterrupted()) { i++; } System.out.println("Count i = " +i); } /** * 關閉 */ public void cancel() { on = false; } } }

      輸出結果:

      總結:

      main線程通過中斷操作和cancel()方法均可使CountThread得以終止。這兩種方法終止線程的好處是能讓線程在終止時有機會去清理資源。做法更加安全和優(yōu)雅。

      文章總結至《java并發(fā)編程的藝術》,下文總結-線程間通信。

      Java

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:打印表格時內容顯示不完整怎么辦?四種方法解決WPS不完整問題
      下一篇:在正式生產前進行PCB打樣的好處
      相關文章
      亚洲国产另类久久久精品黑人| 亚洲视频在线精品| 国产亚洲综合色就色| 亚洲男人天堂2020| 亚洲国产午夜中文字幕精品黄网站| 7777久久亚洲中文字幕| 亚洲AV无码一区二区三区人| 亚洲激情校园春色| 亚洲欧洲国产经精品香蕉网| 亚洲欧洲日产国码二区首页| 亚洲精品mv在线观看| 亚洲欧洲春色校园另类小说| 亚洲午夜一区二区电影院| 亚洲一区免费视频| 亚洲人成电影网站| 国产色在线|亚洲| 亚洲熟女www一区二区三区| 亚洲不卡影院午夜在线观看| 亚洲欧洲精品成人久久曰| 亚洲av无码专区在线观看亚| 国产亚洲精品第一综合| 亚洲国产精品一区二区第一页免| 亚洲人成人无码网www国产| 一本色道久久综合亚洲精品高清| 亚洲中文字幕无码永久在线 | 日本亚洲精品色婷婷在线影院| 亚洲人成综合在线播放| 亚洲熟妇AV一区二区三区宅男| 亚洲国产精品成人午夜在线观看| 爱爱帝国亚洲一区二区三区| 亚洲人成网站在线观看青青| 亚洲中文字幕无码中文字在线| 亚洲AV日韩AV永久无码免下载 | 亚洲第一综合天堂另类专 | 久久亚洲私人国产精品vA| 亚洲成a人片在线观看中文app| 久久亚洲精品国产亚洲老地址| 亚洲AV无码一区二区三区牲色| 亚洲午夜无码片在线观看影院猛| 亚洲欧洲日产国码av系列天堂 | 色欲aⅴ亚洲情无码AV|