02.并發編程(2)Thread類源碼分析

概述

在說線程之前先說下進程,進程和線程都是一個時間段的描述,是CPU工作時間段的描述。

進程,是并發執行的程序在執行過程中分配和管理資源的基本單位,是一個動態概念,竟爭計算機系統資源的基本單位。每一個進程都有一個自己的地址空間,即進程空間或(虛空間)。

線程,在網絡或多用戶環境下,一個服務器通常需要接收大量且不確定數量用戶的并發請求,為每一個請求都創建一個進程顯然是行不通的,——無論是從系統資源開銷方面或是響應用戶請求的效率方面來看。因此,操作系統中線程的概念便被引進了。線程,是進程的一部分,一個沒有線程的進程可以被看作是單線程的。線程有時又被稱為輕權進程或輕量級進程,也是 CPU 調度的一個基本單位。

創建方式

線程的創建有三種方式:繼承Thread,實現Runnable接口,利用Callable跟Future

繼承Thread

(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執行體。 (2)創建Thread子類的實例,即創建了線程對象。 (3)調用線程對象的start()方法來啟動該線程。

    public class FirstMethod extends Thread {@Overridepublic void run() {super.run();}}FirstMethod firstMethod = new FirstMethod();firstMethod.start();
復制代碼

實現Runnable接口

  • (1)定義runnable接口的實現類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
  • (2)創建 Runnable實現類的實例,并依此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象。
  • (3)調用線程對象的start()方法來啟動該線程。
public class SecondMethod implements Runnable{@Overridepublic void run() {}}
SecondMethod secondMethod=new SecondMethod();
new Thread(secondMethod).start();
復制代碼

通過Callable跟FutureTask創建線程

1)創建Callable接口的實現類,并實現call()方法,該call()方法將作為線程執行體,并且有返回值。 (2)創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。 (3)使用FutureTask對象作為Thread對象的target創建并啟動新線程。 (4)調用FutureTask對象的get()方法來獲得子線程執行結束后的返回值

  public class ThirdMethod implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName();}}ThirdMethod thirdMethod=new ThirdMethod();FutureTask<String> futureTask=new FutureTask<String>(thirdMethod);try {String threadName = futureTask.get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} 
復制代碼

對比分析

實現Runnable和實現Callable接口的方式基本相同,不過是后者執行call()方法有返回值,后者線程執行體run()方法無返回值,因此可以把這兩種方式歸為一種這種方式與繼承Thread類的方法之間的差別如下:

1、接口創建線程可以實現資源共享,比如多個線程可以共享一個Runnable資源 2、但是編程稍微復雜,如果需要訪問當前線程,必須調用Thread.currentThread()方法。 3、接口創建線可以避免由于Java的單繼承特性而帶來的局限。

現在通過一個程序員改Bug的例子來描述一下,一共有15個bug,現在安排3個程序員去Debug:

通過Thread來實現

public class BugThread extends Thread {private volatile int bugNumber = 5;@Overridepublic void run() {for (int i = 0; i < bugNumber; i++) {System.out.println("bugNumber--->" + bugNumber--);}}
}public class Main {public static void main(String[] args) {new BugThread().start();new BugThread().start();new BugThread().start();}
}
復制代碼

運行結果:

Thread-0-----5
Thread-1-----5
Thread-2-----5
Thread-0-----4
Thread-2-----4
Thread-1-----4
Thread-2-----3
Thread-0-----3
Thread-2-----2
Thread-1-----3
Thread-2-----1
Thread-0-----2
Thread-0-----1
Thread-1-----2
Thread-1-----1
復制代碼

通過Runnable來實現

public class BugRunnable implements Runnable {private volatile int bugNumber = 15;@Overridepublic void run() {while (bugNumber > 0)System.out.println(Thread.currentThread().getName() + "-----" + bugNumber--);}
}public static void main(String[] args) {BugRunnable bugRunnable = new BugRunnable();new Thread(bugRunnable).start();new Thread(bugRunnable).start();new Thread(bugRunnable).start();}
復制代碼

運行結果

Thread-0-----15
Thread-0-----14
Thread-0-----13
Thread-0-----12
Thread-1-----11
Thread-0-----10
Thread-1-----9
Thread-0-----8
Thread-1-----7
Thread-0-----6
Thread-1-----5
Thread-0-----4
Thread-1-----3
Thread-0-----2
Thread-1-----1
復制代碼

源碼分析

成員變量

	private volatile char  name[];//線程名稱的字節數組private int    priority;//線程優先級private boolean single_step;  //線程是否單步private boolean daemon = false;  //是否是守護線程private boolean stillborn = false; //JVM stateprivate Runnable target;  //從構造方法傳過來的Runnableprivate ThreadGroup group; //線程組private ClassLoader contextClassLoader;  //類加載器private static int threadInitNumber;  //線程編號private volatile int threadStatus = 0; //初始狀態public final static int MIN_PRIORITY = 1; //最低優先級public final static int NORM_PRIORITY = 5; //默認優先級public final static int MAX_PRIORITY = 10; //最高優先級復制代碼

線程狀態

 public enum State {//Thread state for a thread which has not yet started.NEW,//Thread state for a runnable thread. RUNNABLE,//Thread state for a thread blocked waiting for a monitor lock.BLOCKED,// Thread state for a waiting thread.WAITING,//Thread state for a waiting thread with a specified waiting time.TIMED_WAITING,//Thread state for a terminated threadTERMINATED;}
復制代碼

線程的狀態有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,可以整理成如下表格

線程狀態解釋
New還未調用 start() 方法
RUNNABLE調用了 start() ,此時線程已經準備好被執行,處于就緒隊列
BLOCKED線程阻塞于鎖或者調用了 sleep
WAITING線程由于某種原因等待其他線程
TIME_WAITING與 WAITING 的區別是可以在特定時間后自動返回
TERMINATED執行完畢或者被其他線程殺死

構造方法

Thread有很多構造方法,但是通過觀察最終調用了如下方法:

  /*** Initializes a Thread.** @param g //線程組* @param target //構造方法傳過來的Runnable* @param name //線程名稱* @param stackSize //給線程分配的棧的深度* @param acc //上下文加載器*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name.toCharArray();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;//初始化線程組this.daemon = parent.isDaemon();//定義是否為守護線程this.priority = parent.getPriority();//設置優先級if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;//初始化targetsetPriority(priority);//設置優先級if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;//設置棧深度/* Set thread ID */tid = nextThreadID();//設置線程ID}
復制代碼

start方法

    public synchronized void start() {if (threadStatus != 0)//判斷線程是否準備好group.add(this);//將啟動的線程線程組boolean started = false;try {start0();//本地方法,JVM調用target的run方法started = true;//更改啟動標志} finally {try {if (!started)group.threadStartFailed(this);//通知線程組啟動失敗}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}復制代碼
@Overridepublic void run() {if (target != null) {target.run();}}
復制代碼

synchronized 關鍵字說明start方法是同步的,并且是啟動這個線程進行執行,JVM將會調用這個線程的run方法,這樣產生的結果是,兩個線程執行著,其中一個是調用start()方法的線程執行,另一個線程是執行run方法的線程。

sleep()方法

 public static void sleep(long millis, int nanos)throws InterruptedException {if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}sleep(millis);//調用本地方法}
復制代碼

線程休眠一段時間,讓其他線程有機會繼續執行,需要捕捉異常。

yield()方法

  public static native void yield();
復制代碼
  • yield是一個靜態的原生(native)方法
  • yield告訴當前正在執行的線程把運行機會交給線程池中擁有相同優先級的線程。
  • yield不能保證使得當前正在運行的線程迅速轉換到可運行的狀態 它僅能使一個線程從運行狀態轉到可運行狀態,而不是等待或阻塞狀態

join()方法

  public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}
復制代碼

join方法是等待該線程執行,直到超時或者終止,可以作為線程通信的一種方式,A線程調用B線程的join(阻塞),等待B完成后再往下執行。

yieldjoin

  • join方法用線程對象調用,如果在一個線程A中調用另一個線程B的join方法,線程A將會等待線程B執行完畢后再執行。
  • yield可以直接用Thread類調用,yield讓出CPU執行權給同等級的線程,如果沒有相同級別的線程在等待CPU的執行權,則該線程繼續執行。

interrupt()方法

    public void interrupt() {if (this != Thread.currentThread())checkAccess();//檢查權限
synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0();        b.interrupt(this);return;}}interrupt0();}
復制代碼

interrupt()方法是中斷當前的線程, 此外還有isInterrupt,以及interrupted方法

  • interrupt():將線程置為中斷狀態
  • isInterrupt():線程是否中斷
  • interrupted():返回線程的上次的中斷狀態,并清除中斷狀態。 一般來說,阻塞函數:如sleep()、join()、wait()等在檢查到線程的中斷狀態的時候,會拋出InteruptedExeption, 同時會清除線程的中斷狀態。

線程間通信

前面說過,Java中的線程在底層是通過共享內存進行通信的,在應用層則是通過調用Object對象的wait()方法和notify()方法或notifyAll()方法來實現線程間的通信。 Object是所有類的超類,主要通過:notify()、notifyAll()、wait()、wait(long)和wait(long,int)這幾個方法來進行線程間通信。

1、wait()

public final void wait()  throws InterruptedException,IllegalMonitorStateException
復制代碼
  • 休眠當前線程,釋放鎖,直到接到通知或被中斷為止
  • 在調用wait()之前,線程必須要獲得該對象的對象級別鎖

2、notify()

public final native void notify() throws IllegalMonitorStateException
復制代碼
  • 通知那些調用了wait()方法的線程。
  • 每次只能通知單個線程,單個線程等待,則通知當前線程,如果有多個,則隨機抽取一個來進行通知
  • 必須等到當前線程釋放鎖后,wait所在的線程也才可以獲取該對象鎖,但不驚動其他同樣在等待被該對象notify的線程們。
  • wait()等待的是被notify或notifyAll,而不是鎖。

3、notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException
復制代碼
  • 使所有原來在該對象上wait的線程統統退出wait的狀態
  • 所有被通知的線程都會去競爭對象鎖。
  • 獲得鎖的線程,會繼續往下執行,釋放鎖后,wait中的線程繼續競爭對象鎖

wait()和sleep()的區別

  • sleep()方法是線程類Thread的靜態方法,導致此線程暫停執行指定時間,將執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復(線程回到就緒(ready)狀態),因為調用sleep 不會釋放對象鎖。
  • wait()是Object 類的方法,對此對象調用wait()方法導致本線程放棄對象鎖(線程暫停執行),進入等待此對象的等待鎖定池,只有針對此對象發出notify 方法(或notifyAll)后本線程才進入對象鎖定池準備獲得對象鎖進入就緒狀態。

總結

通過對線程源碼的簡單分析,可以看出線程也是有自己的生命周期的,但是由于源碼中有很多native方法,導致了很難追蹤源碼,所以只能大致理解一下線程的各種狀態以及通信過程,下面可以通過一副流程圖來總結一下:

參考資料

Java編程思想

wangchangchung.github.io

www.jianshu.com/p/5b9fdae43…

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/278945.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/278945.shtml
英文地址,請注明出處:http://en.pswp.cn/news/278945.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

安裝SQLserver2008

雙擊點擊setup&#xff0c;以管理員身份運行&#xff1b; 點擊安裝-》全新SQLServer獨立安裝或向現有安裝添加功能 選擇下一步&#xff0c;然后點擊具有高級服務的express版本&#xff0c;點擊下一步&#xff1b; 點擊選擇我接受許可條款&#xff0c;然后繼續點擊下一步&#x…

如何在沒有Word的情況下打開Microsoft Word文檔

Microsoft Word is part of Microsoft Office and requires an up-front purchase or a Microsoft 365 subscription. If you’re using a computer without Word installed, there are other ways to view that DOCX or DOC file. Microsoft Word是Microsoft Office的一部分&a…

redhat9Linux解壓gz,linux (redhat9)下subversion 的安裝

搞了一個subversion 花費了我兩天的時間&#xff0c;其間雖然有干其他的事情&#xff0c;但是來來回回的裝&#xff0c;搞的我是一點脾氣都沒有了&#xff0c;俗話說不經歷風雨真的見不到彩虹。就是這個意思. 原本本的下來一.準備好安裝包打算使用apache來瀏覽subversion &…

數組去重的4種方法(Which one is the fastest???嘻嘻嘻....)

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>Document</title> </head> <body> <input type"button" value"數組去重1" οnclick"show()"&g…

flask中的模型

1.什么是模型   模型&#xff0c;是根據數據庫中表的結構而創建出來的class。每一張表對應到編程語言中&#xff0c;就是一個class表中的每一個列對應到編程語言中就class中的一個屬性。 2.ORM的三大特征   1.數據表(table)到編程類(class)的映射     數據庫中的每一張…

windows復制文件路徑_如何在Windows 10上復制文件的完整路徑

windows復制文件路徑Sometimes, it’s handy to copy the full path of a file or folder in Windows 10 to the clipboard. That way, you can paste the path into an open or upload dialog quickly without having to browse for it the file. Luckily, there’s an easy w…

用c語言復制字符串的元音字母,急求:編寫程序,將一個字符串中的元音字母復制到另一個字符串,然后輸出。...

#include#includevoid str(char a[100],char b[100]){int i0, j0;while(a[i]!\0)//\0代表ASCLL碼0的字符&#xff0c;即是一個空操作符也就是是結束符;{if(a[i]a||a[i]e||a[i]i||a[i]o||a[i]u){b[j]a[i];j;}i;}}int main(){char a[100];char b[100];gets(a);str(a,b);puts(b);r…

05 替換空格

題目描述&#xff1a; 請實現一個函數&#xff0c;將一個字符串中的每個空格替換成“%20”。例如&#xff0c;當字符串為We Are Happy.則經過替換之后的字符串為We%20Are%20Happy。 解題思路有&#xff1a; #判斷字符串是否為空&#xff0c;判斷length是否大于0。 #記錄空格的數…

超鏈接禁用_在Microsoft Word 2003和2007中禁用自動超鏈接

超鏈接禁用If you can’t stand the automatic hyperlinking in Microsoft Word, you might be hard-pressed to find the right place to disable it in Office 2007, since all the settings are hidden so well compared to previous versions. 如果您無法在Microsoft Word中…

c語言如何創建虛擬串口,模擬串口的C語言源程序代碼

本程序是模擬串口硬件機制寫的&#xff0c;使用時可設一定時中斷&#xff0c;時間間隔為1/4波特率&#xff0c;每中斷一次調用一次接收函數&#xff0c; 每中斷4次調用一次發送函數,不過.對單片機來說時鐘并須要快.要知道9600的波特率的每個BIT的時間間隔是104us.而單片機中斷一…

webjars管理靜態資源

webjars用途簡單解釋 : 利用Servlet3協議規范中,包含在JAR文件/META-INF/resources/路徑下的資源可以直接被web訪問到這一原理&#xff0c;將前端靜態資源打成jar包方便管理 靜態資源打jar包 新建maven工程&#xff0c; 將需要打包的靜態資源放入src/main/resources中 2. ma…

Windows Intellij環境下Gradle的 “Could not determine Java version from ‘9.0.1’”的解決方式...

當我導入Gradle項目初試Java spring的時候&#xff0c;遇到下面報錯: Gradle complete project refresh failed Error:Could not determine java version from 9.0.1. 參考這篇 http://www.ddiinnxx.com/solving-not-determine-java-version-9-0-1-gradle-intellij-macosx/ 進行…

如何計算iPhone和Apple Watch上的步數

Khamosh PathakKhamosh PathakAccording to conventional wisdom, 10,000 steps a day equals a healthy life. No matter what your target is, though, you’ll need a reliable way to count your steps. The good news is you can do so on your iPhone or Apple Watch! 根…

在c語言中load,一道題理清Objective-C中的load和initialize

Objective-C中有兩個方法比較特殊&#xff0c;他們會在Runtime時根據情況自動調用&#xff0c;下面我們簡單分析一下調用時機以及使用場景~一般在iOS初中級面試時偶爾會被問到load和initialize方法&#xff0c;我出了一道題&#xff0c;估計會搞暈很多人。大家來看一下下面的程…

018.Zabbix維護時間和模板導入

一 維護時間 在某些正常業務維護期間&#xff0c;不需要進行告警&#xff0c;可添加維護時間。二 維護時間添加 2.1 維護 參數描述Name維護名稱Maintenance type兩種維護類型可選:With data collection - 依舊收集數據No data collection - 暫停收集數據Active since維護周期開…

本地服務器下的局域網安全嗎_本地安全認證服務器

本地服務器下的局域網安全嗎Today a reader had a very good question about lsass.exe is the Microsoft security management process for domain access and local security policies. Simply put it manages who logs on to your PC and/or Server. There are a few viru…

Query-digest-UI監控慢查詢,以及此工具的改進版

本文主要描述基于pt-query-digest工具對慢查詢日志進行監控的工具Query-digest-UI。(安裝、使用、介紹以及benren提供的改進版。) 本文中描述的內容與其他網站上對Query-digest-UI的安裝和使用稍有不同&#xff0c;因為本人對此工具稍做了調整。歡迎轉載&#xff0c;請注明作者…

電熱水器工作過程 c語言,熱水器工作流程圖

燃氣熱水器做為熱水供應設備&#xff0c;被很多家庭所采用&#xff0c;然而&#xff0c;恒溫作為燃氣熱水器的一個痛點&#xff0c;一次次被擊中&#xff0c;那么到底為什么燃氣熱水器實現恒溫這么難呢&#xff1f;我們將從原理講起&#xff0c;帶您認識真正的燃氣熱水器。燃氣…

es6 模塊化

test.js var vm"321321"; export { vm }; ------------------------------------------------------ export var name"李四"; a.vue import {vm} from /test console.log(vm); ------------------------------------------------------ console.log(name);…

linux上tail命令_如何在Linux上使用tail命令

linux上tail命令Fatmawati Achmad Zaenuri/ShutterstockFatmawati Achmad Zaenuri / ShutterstockThe Linux tail command displays data from the end of a file. It can even display updates that are added to a file in real-time. We show you how to use it. Linux tail…