java 并發編程多線程_多線程(一)java并發編程基礎知識

線程的應用

如何應用多線程

在 Java 中,有多種方式來實現多線程。繼承 Thread 類、實現 Runnable 接口、使用 ExecutorService、Callable、Future 實現帶返回結果的多線程。

繼承 Thread 類創建線程

Thread 類本質上是實現了 Runnable 接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過 Thread

類的 start()實例方法。start()方法是一個 native 方法,它會啟動一個新線程,并執行 run()方法。這種方式實現多線程很簡單,

通過自己的類直接 extend Thread,并復寫 run()方法,就可以啟動新線程并執行自己定義的 run()方法。

public classMyThread extends Thread {public voidrun() {

System.out.println("MyThread.run()");

}

}

main中執行

MyThread myThread1= newMyThread();

MyThread myThread2= newMyThread();

myThread1.start();

myThread2.start();

實現 Runnable 接口創建線程

如果自己的類已經 extends 另一個類,就無法直接 extends

Thread,此時,可以實現一個 Runnable 接口

public classMyThread extends OtherClass implements Runnable {public voidrun() {

System.out.println("MyThread.run()");

}

}

實現 Callable 接口通過 FutureTask 包裝器來創建 Thread 線程有的時候,我們可能需要讓一步執行的線程在執行完成以

后,提供一個返回值給到當前的主線程,主線程需要依賴這個值進行后續的邏輯處理,那么這個時候,就需要用到

帶返回值的線程了。Java 中提供了這樣的實現方式

public class CallableDemo implements Callable{public static voidmain(String[] args) throws ExecutionException, InterruptedException {

ExecutorService executorService= Executors.newFixedThreadPool(1);

CallableDemo callableDemo= newCallableDemo();

Future future =executorService.submit(callableDemo);

System.out.println(future.get());

executorService.shutdown();

}

@OverridepublicString call() throws Exception {int a = 1;int b = 2;

System.out.println(a +b);return "執行結果:" + (a +b);

}

}

多線程的實際應用場景

其實大家在工作中應該很少有場景能夠應用多線程了,因為基于業務開發來說,很多使用異步的場景我們都通過分布式消息隊列來做了。但并不是說多線程就不會被用到,

你們如果有看一些框架的源碼,會發現線程的使用無處不在之前我應用得比較多的場景是在做文件跑批,每天會有一些比如收益文件、對賬文件,我們會有一個定時任務去拿

到數據然后通過線程去處理

Java 并發編程的基礎

基本應用搞清楚以后,我們再來基于Java線程的基礎切入,來逐步去深入挖掘線程的整體模型。

線程的生命周期

Java 線程既然能夠創建,那么也勢必會被銷毀,所以線程是存在生命周期的,那么我們接下來從線程的生命周期開始去了解線程。

線程一共有 6 種狀態(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)

NEW:初始狀態,線程被構建,但是還沒有調用 start 方法

RUNNABLED:運行狀態,JAVA 線程把操作系統中的就緒和運行兩種狀態統一稱為“運行中”

BLOCKED:阻塞狀態,表示線程進入等待狀態,也就是線程因為某種原因放棄了 CPU 使用權,阻塞也分為幾種情況

? 等待阻塞:運行的線程執行 wait 方法,jvm 會把當前線程放入到等待隊列

? 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被其他線程鎖占用了,那么 jvm 會把當前的線程放入到鎖池中

? 其他阻塞:運行的線程執行 Thread.sleep 或者 t.join 方法,或者發出了 I/O 請求時,JVM 會把當前線程設置為阻塞狀態,當 sleep 結束、join 線程終止、io 處理完畢則線程恢復

TIME_WAITING:超時等待狀態,超時以后自動返回

TERMINATED:終止狀態,表示當前線程執行完畢

線程狀態圖:

8ac980bd07538675dc553d1c2a879b89.png

演示線程的狀態如下:

import java.util.concurrent.TimeUnit;public classThreadStatus {public static voidmain(String[] args) {//TIME_WAITING

new Thread(() ->{while (true) {try{

TimeUnit.SECONDS.sleep(5);

System.out.println(Thread.currentThread().getName()+"--TimeUnit.SECONDS.sleep(5)");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

},"timewaiting").start();//WAITING,線程在 ThreadStatus 類鎖上通過 wait 進行等待

new Thread(() ->{while (true) {

synchronized (ThreadStatus.class) {try{

ThreadStatus.class.wait();

System.out.println(Thread.currentThread().getName()+"--ThreadStatus.class.wait()");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

},"Waiting").start();//線程在 ThreadStatus 加鎖后,不會釋放鎖

new Thread(new BlockedDemo(), "BlockDemo- 01").start();new Thread(new BlockedDemo(), "BlockDemo- 02").start();

}static classBlockedDemo extends Thread {public voidrun() {

synchronized (BlockedDemo.class) {while (true) {try{

TimeUnit.SECONDS.sleep(3);

System.out.println(Thread.currentThread().getName()+"--TimeUnit.SECONDS.sleep(3)");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

}

運行結果:

D:\Java\jdk1.8.0_91\bin\java.exe "-javaagent:D:\IntelliJ IDEA 2018.2.4\lib\idea_rt.jar=52529:D:\IntelliJ IDEA 2018.2.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Java\jdk1.8.0_91\jre\lib\charsets.jar;D:\Java\jdk1.8.0_91\jre\lib\deploy.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-32.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_91\jre\lib\javaws.jar;D:\Java\jdk1.8.0_91\jre\lib\jce.jar;D:\Java\jdk1.8.0_91\jre\lib\jfr.jar;D:\Java\jdk1.8.0_91\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_91\jre\lib\jsse.jar;D:\Java\jdk1.8.0_91\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_91\jre\lib\plugin.jar;D:\Java\jdk1.8.0_91\jre\lib\resources.jar;D:\Java\jdk1.8.0_91\jre\lib\rt.jar;D:\IntelliJ IDEA Projects\springbootRabbitmq\target\classes;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-web\2.0.0.RELEASE\spring-boot-starter-web-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter\2.0.0.RELEASE\spring-boot-starter-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-logging\2.0.0.RELEASE\spring-boot-starter-logging-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\DevlopeConfig\mavenRepository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\DevlopeConfig\mavenRepository\org\apache\logging\log4j\log4j-to-slf4j\2.10.0\log4j-to-slf4j-2.10.0.jar;D:\DevlopeConfig\mavenRepository\org\apache\logging\log4j\log4j-api\2.10.0\log4j-api-2.10.0.jar;D:\DevlopeConfig\mavenRepository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;D:\DevlopeConfig\mavenRepository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\DevlopeConfig\mavenRepository\org\yaml\snakeyaml\1.19\snakeyaml-1.19.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-json\2.0.0.RELEASE\spring-boot-starter-json-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\jackson\core\jackson-databind\2.9.4\jackson-databind-2.9.4.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\jackson\core\jackson-core\2.9.4\jackson-core-2.9.4.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.4\jackson-datatype-jdk8-2.9.4.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.4\jackson-datatype-jsr310-2.9.4.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.4\jackson-module-parameter-names-2.9.4.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-tomcat\2.0.0.RELEASE\spring-boot-starter-tomcat-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\apache\tomcat\embed\tomcat-embed-core\8.5.28\tomcat-embed-core-8.5.28.jar;D:\DevlopeConfig\mavenRepository\org\apache\tomcat\embed\tomcat-embed-el\8.5.28\tomcat-embed-el-8.5.28.jar;D:\DevlopeConfig\mavenRepository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.28\tomcat-embed-websocket-8.5.28.jar;D:\DevlopeConfig\mavenRepository\org\hibernate\validator\hibernate-validator\6.0.7.Final\hibernate-validator-6.0.7.Final.jar;D:\DevlopeConfig\mavenRepository\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;D:\DevlopeConfig\mavenRepository\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;D:\DevlopeConfig\mavenRepository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-web\5.0.4.RELEASE\spring-web-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-beans\5.0.4.RELEASE\spring-beans-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-webmvc\5.0.4.RELEASE\spring-webmvc-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-aop\5.0.4.RELEASE\spring-aop-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-context\5.0.4.RELEASE\spring-context-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-expression\5.0.4.RELEASE\spring-expression-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-core\5.0.4.RELEASE\spring-core-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-jcl\5.0.4.RELEASE\spring-jcl-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-devtools\2.0.0.RELEASE\spring-boot-devtools-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot\2.0.0.RELEASE\spring-boot-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-autoconfigure\2.0.0.RELEASE\spring-boot-autoconfigure-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\projectlombok\lombok\1.16.20\lombok-1.16.20.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-data-jpa\2.0.0.RELEASE\spring-boot-starter-data-jpa-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-aop\2.0.0.RELEASE\spring-boot-starter-aop-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\aspectj\aspectjweaver\1.8.13\aspectjweaver-1.8.13.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-jdbc\2.0.0.RELEASE\spring-boot-starter-jdbc-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\com\zaxxer\HikariCP\2.7.8\HikariCP-2.7.8.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-jdbc\5.0.4.RELEASE\spring-jdbc-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\hibernate\hibernate-core\5.2.14.Final\hibernate-core-5.2.14.Final.jar;D:\DevlopeConfig\mavenRepository\org\hibernate\javax\persistence\hibernate-jpa-2.1-api\1.0.0.Final\hibernate-jpa-2.1-api-1.0.0.Final.jar;D:\DevlopeConfig\mavenRepository\org\javassist\javassist\3.22.0-GA\javassist-3.22.0-GA.jar;D:\DevlopeConfig\mavenRepository\antlr\antlr\2.7.7\antlr-2.7.7.jar;D:\DevlopeConfig\mavenRepository\org\jboss\jandex\2.0.3.Final\jandex-2.0.3.Final.jar;D:\DevlopeConfig\mavenRepository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar;D:\DevlopeConfig\mavenRepository\org\hibernate\common\hibernate-commons-annotations\5.0.1.Final\hibernate-commons-annotations-5.0.1.Final.jar;D:\DevlopeConfig\mavenRepository\javax\transaction\javax.transaction-api\1.2\javax.transaction-api-1.2.jar;D:\DevlopeConfig\mavenRepository\org\springframework\data\spring-data-jpa\2.0.5.RELEASE\spring-data-jpa-2.0.5.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\data\spring-data-commons\2.0.5.RELEASE\spring-data-commons-2.0.5.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-orm\5.0.4.RELEASE\spring-orm-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-tx\5.0.4.RELEASE\spring-tx-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-aspects\5.0.4.RELEASE\spring-aspects-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\mysql\mysql-connector-java\5.1.38\mysql-connector-java-5.1.38.jar;D:\DevlopeConfig\mavenRepository\org\springframework\boot\spring-boot-starter-amqp\2.0.0.RELEASE\spring-boot-starter-amqp-2.0.0.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\spring-messaging\5.0.4.RELEASE\spring-messaging-5.0.4.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\amqp\spring-rabbit\2.0.2.RELEASE\spring-rabbit-2.0.2.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\springframework\amqp\spring-amqp\2.0.2.RELEASE\spring-amqp-2.0.2.RELEASE.jar;D:\DevlopeConfig\mavenRepository\com\rabbitmq\amqp-client\5.1.2\amqp-client-5.1.2.jar;D:\DevlopeConfig\mavenRepository\com\rabbitmq\http-client\1.3.1.RELEASE\http-client-1.3.1.RELEASE.jar;D:\DevlopeConfig\mavenRepository\org\apache\httpcomponents\httpclient\4.5.5\httpclient-4.5.5.jar;D:\DevlopeConfig\mavenRepository\org\apache\httpcomponents\httpcore\4.4.9\httpcore-4.4.9.jar;D:\DevlopeConfig\mavenRepository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\DevlopeConfig\mavenRepository\org\springframework\retry\spring-retry\1.2.2.RELEASE\spring-retry-1.2.2.RELEASE.jar"com.lf.Configuration.ThreadStatus

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

timewaiting--TimeUnit.SECONDS.sleep(5)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

timewaiting--TimeUnit.SECONDS.sleep(5)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

timewaiting--TimeUnit.SECONDS.sleep(5)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

timewaiting--TimeUnit.SECONDS.sleep(5)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

timewaiting--TimeUnit.SECONDS.sleep(5)

BlockDemo- 01--TimeUnit.SECONDS.sleep(3)

啟動一個線程前,最好為這個線程設置線程名稱,因為這樣在使用 jstack 分析程序或者進行問題排查時,就會給開發人員提供一些提示顯示線程的狀態

? 運行該示例,打開終端或者命令提示符,鍵入“jps”,

JDK1.5 提供的一個顯示當前所有 java 進程 pid 的命令)

? 根據上一步驟獲得的 pid,繼續輸入 jstack pid(jstack是 java 虛擬機自帶的一種堆棧跟蹤工具。jstack 用于打印出給定的 java 進程 ID 或 core file 或遠程調試服務的 Java 堆棧信息)

通過上面的分析,我們了解到了線程的生命周期,現在在整個生命周期中并不是固定的處于某個狀態,而是隨著代碼的執行在不同的狀態之間進行切換

內容如下:

4384ThreadStatus8084

4360Jps

bash-3.1$ jstack 4384

2020-05-06 23:20:41Full thread dump Java HotSpot(TM) Client VM (25.91-b14 mixed mode):"DestroyJavaVM" #15 prio=5 os_prio=0 tid=0x0342b400 nid=0x30e0 waiting on condition [0x00000000]

java.lang.Thread.State: RUNNABLE"BlockDemo- 02" #14 prio=5 os_prio=0 tid=0x161d1c00 nid=0x3164 waiting for monitor entry [0x167cf000]

java.lang.Thread.State: BLOCKED (onobjectmonitor)

at com.lf.Configuration.ThreadStatus$BlockedDemo.run(ThreadStatus.java:41)- waiting to lock <0x05661d28> (a java.lang.Class forcom.lf.Configuration.ThreadStatus$BlockedDemo)

at java.lang.Thread.run(Thread.java:745)"BlockDemo- 01" #12 prio=5 os_prio=0 tid=0x161cf000 nid=0x24c8 waiting on condition [0x1673f000]

java.lang.Thread.State: TIMED_WAITING (sleeping)

at java.lang.Thread.sleep(Native Method)

at java.lang.Thread.sleep(Thread.java:340)

at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)

at com.lf.Configuration.ThreadStatus$BlockedDemo.run(ThreadStatus.java:41)- locked <0x05661d28> (a java.lang.Class forcom.lf.Configuration.ThreadStatus$BlockedDemo)

at java.lang.Thread.run(Thread.java:745)"Waiting" #10 prio=5 os_prio=0 tid=0x161cdc00 nid=0x1bd8 in Object.wait() [0x166af000]

java.lang.Thread.State: WAITING (onobjectmonitor)

at java.lang.Object.wait(Native Method)- waiting on <0x05ad2000> (a java.lang.Class forcom.lf.Configuration.ThreadStatus)

at java.lang.Object.wait(Object.java:502)

at com.lf.Configuration.ThreadStatus.lambda$main$1(ThreadStatus.java:23)- locked <0x05ad2000> (a java.lang.Class forcom.lf.Configuration.ThreadStatus)

at com.lf.Configuration.ThreadStatus$$Lambda$2/30452001.run(Unknown Source)

at java.lang.Thread.run(Thread.java:745)"timewaiting" #9 prio=5 os_prio=0 tid=0x161cd000 nid=0x1298 waiting on condition [0x1661f000]

java.lang.Thread.State: TIMED_WAITING (sleeping)

at java.lang.Thread.sleep(Native Method)

at java.lang.Thread.sleep(Thread.java:340)

at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)

at com.lf.Configuration.ThreadStatus.lambda$main$0(ThreadStatus.java:11)

at com.lf.Configuration.ThreadStatus$$Lambda$1/8844017.run(Unknown Source)

at java.lang.Thread.run(Thread.java:745)"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x160a3c00 nid=0x1e24 runnable [0x00000000]

java.lang.Thread.State: RUNNABLE"C1 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x16063c00 nid=0x1a64 waiting on condition [0x00000000]

java.lang.Thread.State: RUNNABLE"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x160a6800 nid=0xc58 runnable [0x163df000]

java.lang.Thread.State: RUNNABLE

at java.net.SocketInputStream.socketRead0(Native Method)

at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)

at java.net.SocketInputStream.read(SocketInputStream.java:170)

at java.net.SocketInputStream.read(SocketInputStream.java:141)

at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)

at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)

at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)- locked <0x05aee288>(a java.io.InputStreamReader)

at java.io.InputStreamReader.read(InputStreamReader.java:184)

at java.io.BufferedReader.fill(BufferedReader.java:161)

at java.io.BufferedReader.readLine(BufferedReader.java:324)- locked <0x05aee288>(a java.io.InputStreamReader)

at java.io.BufferedReader.readLine(BufferedReader.java:389)

at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x15a91000 nid=0x1230 waiting on condition [0x00000000]

java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x15a8f800 nid=0x3150 runnable [0x00000000]

java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x15a7d000 nid=0x11f8 in Object.wait() [0x15dcf000]

java.lang.Thread.State: WAITING (onobjectmonitor)

at java.lang.Object.wait(Native Method)- waiting on <0x05aee758> (a java.lang.ref.ReferenceQueue$Lock)

at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)- locked <0x05aee758> (a java.lang.ref.ReferenceQueue$Lock)

at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)

at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x15a67400 nid=0x1b8c in Object.wait() [0x15d3f000]

java.lang.Thread.State: WAITING (onobjectmonitor)

at java.lang.Object.wait(Native Method)- waiting on <0x05aee8f8> (a java.lang.ref.Reference$Lock)

at java.lang.Object.wait(Object.java:502)

at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x05aee8f8> (a java.lang.ref.Reference$Lock)

at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"VM Thread" os_prio=2 tid=0x15a61400 nid=0x1ec4runnable"VM Periodic Task Thread" os_prio=2 tid=0x1619ac00 nid=0xfe0waiting on condition

JNIglobal references: 232bash-3.1$

線程的啟動

前面我們通過一些案例演示了線程的啟動,也就是調用start()方法去啟動一個線程,當 run 方法中的代碼執行完畢以后,線程的生命周期也將終止。

調用 start 方法的語義是當前線程告訴 JVM,啟動調用 start 方法的線程。

線程的啟動原理

很多同學最早學習線程的時候會比較疑惑,啟動一個線程為什么是調用 start 方法,而不是 run 方法,這做一個簡單的分析,先簡單看一下 start 方法的定義

7f9678d68e55f2980e19bdfe1f5aee37.png

我們看到調用 start 方法實際上是調用一個 native 方法start0()來啟動一個線程,首先 start0()這個方法是在Thread 的靜態塊中來注冊的,代碼如下

303cbc57f1957f40561570d2b925ea1c.png

更深入的。。。囧

線程的終止

線程的啟動過程大家都非常熟悉,但是如何終止一個線程呢? 這是面試過程中針對 3 年左右的人喜歡問到的一個題目。

線程的終止,并不是簡單的調用 stop 命令去。雖然 api 仍然可以調用,但是和其他的線程控制方法如 suspend、resume 一樣都是過期了的不建議使用,

就拿 stop 來說,stop 方法在結束一個線程時并不會保證線程的資源正常釋放,因此會導致程序可能出現一些不確定的狀態。

要優雅的去中斷一個線程,在線程中提供了一個 interrupt方法

interrupt 方法

當其他線程通過調用當前線程的 interrupt 方法,表示向當前線程打個招呼,告訴他可以中斷線程的執行了,至于什

么時候中斷,取決于當前線程自己。線程通過檢查資深是否被中斷來進行相應,可以通過isInterrupted()來判斷是否被中斷。

通過下面這個例子,來實現了線程終止的邏輯

public classInterruptDemo {private static inti;public static voidmain(String[] args) throws InterruptedException {

Thread thread= new Thread(() ->{while (!Thread.currentThread().isInterrupted()) { //默認情況下isInterrupted 返回 false、通過 thread.interrupt 變成了 true

i++;

}

System.out.println("Num:" +i);

},"interruptDemo");

thread.start();

TimeUnit.SECONDS.sleep(1);

thread.interrupt();//加和不加的效果

}

}

加了,線程運行一會會被打斷,并輸出Num值

不加,線程會一直運行

Thread.interrupted

上面的案例中,通過 interrupt,設置了一個標識告訴線程可 以 終 止 了 , 線 程 中 還 提 供 了 靜 態 方 法Thread.interrupted()對設置中斷標識的線程復位。比如在

上面的案例中,外面的線程調用 thread.interrupt 來設置中斷標識,而在線程里面,又通過 Thread.interrupted 把線程的標識又進行了復位

public classInterruptDemo2 {private static inti;public static voidmain(String[] args) throws InterruptedException {

Thread thread= new Thread(() ->{while (true) {if(Thread.currentThread().isInterrupted()) {

System.out.println("before:" +Thread.currentThread().isInterrupted());

Thread.interrupted();//對線程進行復位,由 true 變成 false

System.out.println("after:" +Thread.currentThread().isInterrupted());

}

}

},"interruptDemo");

thread.start();

TimeUnit.SECONDS.sleep(1);

thread.interrupt();

}

}

運行結果:

before:trueafter:false

其他的線程復位

除了通過 Thread.interrupted 方法對線程中斷標識進行復 位 以 外 , 還 有 一 種 被 動 復 位 的 場 景 , 就 是 對 拋 出 InterruptedException 異 常 的 方 法 ,

在 InterruptedException 拋出之前,JVM 會先把線程的中斷 標識位清除,然后才會拋出 InterruptedException,這個時 候如果調用 isInterrupted 方法,

將會返回 false 分別通過下面兩個 demo 來演示復位的效果

demo1public static voidmain(String[] args) throws InterruptedException {

Thread thread= new Thread(() ->{while (!Thread.currentThread().isInterrupted()) {

i++;

}

System.out.println("Num:" +i);

},"interruptDemo");

thread.start();

TimeUnit.SECONDS.sleep(1);

thread.interrupt();

System.out.println(thread.isInterrupted());

}

輸出:

true

Num:36375203

private static inti;

demo2public static voidmain(String[] args) throws InterruptedException {

Thread thread= new Thread(() ->{while (!Thread.currentThread().isInterrupted()) {try{

TimeUnit.SECONDS.sleep(1);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("Num:" +i);

},"interruptDemo");

thread.start();

TimeUnit.SECONDS.sleep(1);

thread.interrupt();

System.out.println(thread.isInterrupted());

}

輸出:

false

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at java.lang.Thread.sleep(Thread.java:340)

at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)

at com.lf.Configuration.InterruptDemoCompare.lambda$main$0(InterruptDemoCompare.java:12)

at java.lang.Thread.run(Thread.java:745)

為什么要復位

Thread.interrupted()是屬于當前線程的,是當前線程對外界中斷信號的一個響應,表示自己已經得到了中斷信號,

但不會立刻中斷自己,具體什么時候中斷由自己決定,讓外界知道在自身中斷前,他的中斷狀態仍然是 false,這就是復位的原因。

線程的終止原理

我們來看一下 thread.interrupt()方法做了什么事情

720949b8345b88a958694d6570a2be50.png

這個方法里面,調用了 interrupt0(),這個方法在前面分析start 方法的時候見過,是一個 native 方法,這里就不再重

復貼代碼了,同樣,我們找到 jvm.cpp 文件,找到JVM_Interrupt 的定義

e57157dc5e221471b3f872f88033ce15.png

這個方法比較簡單,直接調用了 Thread::interrupt(thr)這個方法,這個方法的定義在 Thread.cpp 文件中,代碼如下

d79557bde437d5f156ecf7d8dd555a91.png

Thread::interrupt 方法調用了 os::interrupt 方法,這個是調用平臺的 interrupt 方法,這個方法的實現是在 os_*.cpp

文件中,其中星號代表的是不同平臺,因為 jvm 是跨平臺的,所以對于不同的操作平臺,線程的調度方式都是不一樣的。

我們以 os_linux.cpp 文件為例

9b9aac96cd2671c6c8c9da08b9d79c60.png

set_interrupted(true)實際上就是調用 osThread.hpp 中的

set_interrupted()方法,在 osThread 中定義了一個成員屬

性 volatile jint _interrupted;

通過上面的代碼分析可以知道,thread.interrupt()方法實際就是設置一個 interrupted 狀態標識為 true、并且通過

ParkEvent 的 unpark 方法來喚醒線程。

1. 對于 synchronized 阻塞的線程,被喚醒以后會繼續嘗試獲取鎖,如果失敗仍然可能被 park

2. 在調用 ParkEvent 的 park 方法之前,會先判斷線程的中斷狀態,如果為 true,會清除當前線程的中斷標識

3. Object.wait 、 Thread.sleep 、 Thread.join 會 拋 出InterruptedException

這里給大家普及一個知識點,為什么 Object.wait、Thread.sleep和Thread.join

會 拋 出InterruptedException? 你會發現這幾個方法有一個共同點,都是屬于阻塞的方法

而阻塞方法的釋放會取決于一些外部的事件,但是阻塞方法可能因為等不到外部的觸發事件而導致無法終止,所以它允許一個線程請求自己來停止它正在做的事情。

當一個方法拋出 InterruptedException 時,它是在告訴調用者如果執行該方法的線程被中斷,它會嘗試停止正在做的事情

并且通過拋出 InterruptedException 表示提前返回。所以,這個異常的意思是表示一個阻塞被其他線程中斷了。

然 后 , 由 于 線 程 調 用 了 interrupt() 中 斷 方 法 , 那 么Object.wait、Thread.sleep 等被阻塞的線程被喚醒以后會

通過 is_interrupted 方法判斷中斷標識的狀態變化,如果發現中斷標識為 true,則先清除中斷標識,然后拋出InterruptedException

需要注意的是,InterruptedException 異常的拋出并不意味著線程必須終止,而是提醒當前線程有中斷的操作發生,

至于接下來怎么處理取決于線程本身,比如

1. 直接捕獲異常不做任何處理

2. 將異常往外拋出

3. 停止當前線程,并打印異常信息

為 了 讓 大 家 能 夠 更 好 的 理 解 上 面 這 段 話 , 我 們 以Thread.sleep 為例直接從 jdk 的源碼中找到中斷標識的清

除以及異常拋出的方法代碼找 到 is_interrupted() 方法, linux 平 臺 中 的 實 現 在

os_linux.cpp 文件中,代碼如下

b698b73fe30a78c9c8bba4a2634b5ec8.png

找到 Thread.sleep 這個操作在 jdk 中的源碼體現,怎么找?相信如果前面大家有認真看的話,應該能很快找到,

代碼在 jvm.cpp 文件中

4a9bbffdd8892a2ac2aaafb9a6c1c1e2.png

注意上面加了中文注釋的地方的代碼,先判斷is_interrupted 的 狀 態 , 然 后 拋 出 一 個

InterruptedException 異常。到此為止,我們就已經分析清楚了中斷的整個流程。

之前看 zookeeper 源碼的時候看到一個比較有意思的異步責任鏈模式

package com.lf.Configuration;

import lombok.Getter;

import lombok.Setter;

import lombok.ToString;

@ToStringpublic classRequest {

@Getter

@SetterprivateString name;

}

package com.lf.Configuration;public interfaceRequestProcessor {voidprocessRequest(Request request);

}

package com.lf.Configuration;

import java.util.concurrent.LinkedBlockingQueue;public classSaveProcessor extends Thread implements RequestProcessor {

LinkedBlockingQueue requests = new LinkedBlockingQueue();

@Overridepublic voidrun() {while (true) {try{//隊列為空,阻塞等待。//隊列不為空,從隊首獲取并移除一個元素,如果消費后還有元素在隊列中,繼續喚醒下一個消費線程進行元素移除。//如果放之前隊列是滿元素的情況,移除完后要喚醒生產線程進行添加元素

System.out.println("SaveProcessor: begin");

Request request= requests.take();//System.out.println("save request info:" +request);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}//處理請求

public voidprocessRequest(Request request) {

requests.add(request);

}

}

package com.lf.Configuration;

import java.util.concurrent.LinkedBlockingQueue;public classPrintProcessor extends Thread implements RequestProcessor {

LinkedBlockingQueue requests = new LinkedBlockingQueue<>();privatefinal RequestProcessor nextProcessor;publicPrintProcessor(RequestProcessor nextProcessor) {this.nextProcessor =nextProcessor;

}

@Overridepublic voidrun() {while (true) {try{//隊列為空,阻塞等待。//隊列不為空,從隊首獲取并移除一個元素,如果消費后還有元素在隊列中,繼續喚醒下一個消費線程進行元素移除。//如果放之前隊列是滿元素的情況,移除完后要喚醒生產線程進行添加元素

System.out.println("PrintProcessor: begin");

Request request=requests.take();

System.out.println("print data:" +request.getName());

nextProcessor.processRequest(request);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}//處理請求

public voidprocessRequest(Request request) {

requests.add(request);

}

}

package com.lf.Configuration;public classMain {

PrintProcessor printProcessor;protectedMain() {

SaveProcessor saveProcessor= newSaveProcessor();

saveProcessor.start();

printProcessor= newPrintProcessor(saveProcessor);

printProcessor.start();

}private voiddoTest(Request request) {

printProcessor.processRequest(request);

}public static voidmain(String[] args) {

Request request= newRequest();

request.setName("lf");newMain().doTest(request);

}

}

運行結果:

SaveProcessor: begin

PrintProcessor: begin

print data:lf

PrintProcessor: begin

save request info:Request(name=lf)

SaveProcessor: begin

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

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

相關文章

Docker監控方案(TIG)的研究與實踐之Influxdb

2019獨角獸企業重金招聘Python工程師標準>>> 前言&#xff1a; Influxdb也是有influxdata公司(www.influxdata.com )開發的用于數據存儲的時間序列數據庫.可用于數據的時間排列。在整個TIG(Telegrafinfluxdbgrafana)方案中&#xff0c;influxdb可算作一個中間件&…

iOS-生成隨機數

有時候我們需要在程序中生成隨機數&#xff0c;但是在Objective-c中并沒有提供相應的函數&#xff0c;好在C中提供了rand()、srand()、random()、arc4random()幾個函數。那么怎么使用呢&#xff1f;下面將簡單介紹&#xff1a; 1、 獲取一個隨機整數范圍在&#xff1a;[0,100)…

劍指offer 面試32題

面試32題&#xff1a; 題目&#xff1a;從上到下打印二叉樹 題&#xff1a;不分行從上到下打印二叉樹 解題代碼&#xff1a; # -*- coding:utf-8 -*- # class TreeNode: # def __init__(self, x): # self.val x # self.left None # self.right …

crc算法java_c語言的crc16算法轉java

一、c語言uint16_t crc_chk(uint8_t *data, uint8_t len){uint8_t i;uint16_t reg_crc 0xffff;while(len--) {reg_crc ^ *data;for(i 0; i < 8; i) {if(reg_crc & 0x01) {reg_crc (reg_crc >> 1) ^ 0xA001;} else {reg_crc reg_crc >> 1;}}}return reg_…

Java第三階段學習(三、字符流、轉換流)

一、字節流讀取中文時出現的問題&#xff1a; 文件中有中文時&#xff0c;用字節流讀取會出現亂碼的問題&#xff0c;因為一個中文為兩個字節。 二、字符編碼表 編碼表&#xff1a;其實就是生活中字符和計算機二進制的對應關系表。 1、ascii&#xff1a; 一個字節中的7位就可以…

windows下OpenSSL加密證書安裝步驟與使用方法

OpenSSL加密證書一般用于簽名認證&#xff0c;含私鑰和公鑰。在Linux系統中&#xff0c;OpenSSL一般是已經安裝好了&#xff0c;可以直接使用。而在Windows系統中&#xff0c;是需要安裝使用的。 最近在使用支付平臺時&#xff0c;用到了OpenSSL&#xff0c;鑒于此分享給大家&a…

linux運維(五)

頭有點大&#xff0c;也浪費了一些時間。明天過后緩沖一下1、存儲過程與觸發器的區別&#xff1f;答&#xff1a;&#xff08;1&#xff09;觸發器主要是通過事件執行觸發而被執行的&#xff0c;而存儲過程可以通過存儲過程名稱而直接調用。&#xff08;call/execute&#xff0…

django web 自定義通用權限控制

需求&#xff1a;web系統有包含以下5個url&#xff0c;分別對于不同資源&#xff1b; 1、stu/add_stu/ 2、stu/upload_homework/ 3、stu/query_homework/ 4、stu/add_record/ ----------------------------------------------------------------------------------------------…

java源碼影視源碼搭建教程_新版千月影視app源碼+搭建教程

使用notepad批量替換URL【http://】為你的域名(被替換的域名訪問有成品不能發布 需要修改的到前臺confing里面查詢)&#xff0c;替換名稱【鯨鷹影視】為你的應用名稱&#xff1b;服務端&#xff1a;1.將替換好的后端源碼打包上傳至站點根目錄后解壓&#xff1b;2.配置網站偽靜態…

git學習相關的博客地址

Git分支管理策略&#xff1a; http://www.ruanyifeng.com/blog/2012/07/git.html Git 使用規范流程&#xff1a; http://www.ruanyifeng.com/blog/2015/08/git-use-process.html 基于git的源代碼管理模型——git flow&#xff1a; http://www.ituring.com.cn/article/56870 Git…

Django框架基礎學習

Django安裝python下載地址 http://www.python.org/download/releases/3.3.4/Django的下載地址&#xff1a;https://www.djangoproject.com/download/1&#xff09;安裝&#xff08;進到解壓目錄&#xff09;python setup.py install2、配置環境變量&#xff08;PATH&#xff09…

Uboot USB模式(RK3288變磚頭的解決辦法)

RK3288啟動后有三種模式&#xff0c;可以分別進行操作。 第一種是normal也就是正常的啟動模式。這個模式無法刷固件。一般板子通電就是這個模式 第二種是loader模式。就是刷固件模式。這個模式可以刷各種image。按住recover按鍵再通電&#xff0c;通過uboot的檢測進入這個模式 …

java郵件實例_java郵件小實例

新建一個包&#xff0c;名為mail第一個類&#xff1a;MailSenderInfo.java###########################################package com.util.mail;/*** 發送郵件需要使用的基本信息*author by wangfunhttp://www.5a520.cn 小說520*/import java.util.Properties;public class Mai…

DEV GridView嵌套

/// <summary> /// 綁定主表和明顯表到GridView /// </summary> /// <param name"machineProduct">主表</param> /// <param name"configureData">字表</param> private void Mas…

局域網大型文件分發的可能解決方案

客戶原來的做法是把文件上傳到服務器&#xff0c;然后后形成一個普通的HTTP地址下入網站后臺系統&#xff0c;然后客戶端用戶看到后&#xff0c;則下載下來。但是隨著文件越來越大&#xff0c;客戶端下載量增加&#xff0c;在局域內網環境中這種文件分發方式的弊端立現。服務器…

android——獲取ImageView上面顯示的圖片bitmap對象

獲取的函數方法為&#xff1a;Bitmap bitmapimageView.getDrawingCache(); 但是如果只是這樣寫我們得到的bitmap對象可能為null值&#xff0c;正確的方式為&#xff1a; imageView.setDrawingCacheEnabled(true);Bitmap bitmapimageView.getDrawingCache();imageView.setDrawin…

java監聽com口_簡單了解Java接口+事件監聽機制

1.接口&#xff1a;定義方法&#xff1a;public interface interName //extends interName2, interName3...可繼承多個接口在接口里只能定義常量和抽象方法。public static final String Name;public abstract void method(String Name);//這里不能用大括號&#xff0c;不然就不…

例子:好友列表選中效果

<style type"text/css"> *{ margin:0px auto; padding:0px; font-family:微軟雅黑; font-size:16px;} .f{ width:200px; height:30px; background-color:#63C; color:white; text-align:center; line-height:30px; vertical-align:middle; margin-top:3px} .f:…

sublime 常用插件

AutoFileName 文件提示路徑&#xff0c;在img,script的[src]屬性。link,a的[href]屬性&#xff0c;background 的[url]屬性后提示文件的路徑 CSS Format css格式化工具 Pretty JSON json格式化工具轉載于:https://www.cnblogs.com/zhangtao1990/p/9231608.html

有一句說一千句,是作家....

有一句說一千句&#xff0c;是作家&#xff0c;這叫文采&#xff1b;有一句說一百句&#xff0c;是演說家&#xff0c;這叫口才&#xff1b;有一句說十句&#xff0c;是教授&#xff0c;這叫學問&#xff1b;有一句說一句&#xff0c;是律師&#xff0c;這叫嚴謹&#xff1b;說…