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.mzph.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;说…