java是一个天生就支持多线程的语言。
查看JVM内的线程
public static void main(String[] args) {
//虚拟机线程管理的接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos =
threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos) {
System.out.println("["+threadInfo.getThreadId()+"]"+" "
+threadInfo.getThreadName());
}
}
[8] JDWP Command Reader
[7] JDWP Event Helper Thread
[6] JDWP Transport Listener: dt_socket
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
我们发现启动一个main方法,JVM虚拟机会给我们启动8个线程。
新建线程的3种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
Callable和Runnable的区别
- Callable有返回值,Runnable没有。
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
- Runnable的执行 new Thread(runnable).start(); Callable的执行需要用到FutureTask。
Runnable接口
@Test
public void runnableTest() {
// JDK 1.7
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "::Runnable异步任务1");
}
}).start();
// JDK 1.8
new Thread(() -> System.out.println(Thread.currentThread().getName() + "::Runnable异步任务2")).start();
}
输出结果:
Thread-3::Runnable异步任务1
Thread-4::Runnable异步任务2
实现Callable接口
@Test
public void callableTest() {
ExecutorService service = Executors.newFixedThreadPool(10);
// JDK 1.7
FutureTask<String> futureTask1 = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + "::Callable异步任务1";
}
});
// JDK 1.8
FutureTask<String> futureTask2 = new FutureTask<>(() -> Thread.currentThread().getName() + "::Callable异步任务2");
service.submit(futureTask1);
service.submit(futureTask2);
try {
System.out.println(futureTask1.get(5, TimeUnit.SECONDS));
System.out.println(futureTask2.get(5, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
输出结果:
pool-1-thread-1::Callable异步任务1
pool-1-thread-2::Callable异步任务2
停止一个线程
上面的示例是如和新建并启动一个异步线程,下面我们来说一下如何停止一个线程。
- 线程自然终止(自然执行完或抛出未处理异常)
- stop():这是一个抢占式方法不建议使用,stop()是直接停止线程,而不管该线程的资源是否已经释放掉了。
- suspend(),resume(): 线程的挂起与恢复,也不建议使用,因为suspend在调用过程中不会释放其占用的共享资源如:锁,容易引起死锁。
- interrupt():该方法是一个协作式方法,并不会强制关闭线程,他只是将线程的中断标志位设置成true(中断线程),默认是false(不中断线程),线程是否中断由线程自己决定,推荐使用这种方式去关闭线程。
使用interrupt中断线程,我们必须在线程内部对线程的中断状态进行处理,否则即使调用了interrupt()
方法也不会有效果。那么我们怎么知道线程中断状态呢,有两个方法可以知道线程是否处于中断状态:
-
isInterrupted()
:它是一个无副作用的方法,可以重复调用。 - 静态的
interrupted()
:它是一个有无副作用的方法,在调用的时候会同时将中断标志为设置成false。
**注意:**方法里如果抛出InterruptedException
异常,线程的中断标志位会被复位成false,如果确实是需要中断线程,我们需要自己在catch语句块里再次调用interrupt()
方法。
/**
* 中断线程有异常
*
* @author yuhao.wang3
*/
public class HasInterrputException {
public static void main(String[] args) throws InterruptedException {
Thread endThread = new Thread(() -> {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("UseThread:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(threadName + " catch interrput flag is " + Thread.currentThread().isInterrupted() +
" at " + (LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS"))));
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println(threadName);
}
System.out.println(threadName + " interrput flag is " + Thread.currentThread().isInterrupted());
});
endThread.start();
System.out.println("Main:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
Thread.sleep(800);
System.out.println("Main begin interrupt thread:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
endThread.interrupt();
}
}
输出结果:
Main:2019-03-20 19:57:35_221
thread:2019-03-20 19:57:35_221
Main begin interrupt thread:2019-03-20 19:57:36_035
Thread-0 catch interrput flag is false at 2019-03-20 19:57:36_035
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xiaolyuh.HasInterrputException.lambda$main$0(HasInterrputException.java:18)
at java.lang.Thread.run(Thread.java:745)
Thread-0
Thread-0 interrput flag is true
Process finished with exit code 0
如果将 catch里面的Thread.currentThread().interrupt();
去掉,那么线程就会永远执行。
线程的几种状态
我们刚刚说了线程的启动的和停止,那么一个线程到底有几种状态呢?
我们线程一共有5个状态,几种状态的扭转看上图。
yield():方法也是一个协作式方法,他会让出CPU执行权,但是下一次CPU调度的时候该线程依然有可能再次获取到CPU执行权,并执行。
sleep():阻塞线程,让出CPU执行权,但是不会释放锁,到了时间后会继续执行;状态切换到阻塞和可运行都会产生上下文切换;多用于使当前线程等待;这是Thread的方法,不需要获取到锁就可以执行。
wait():阻塞线程,让出CPU执行权,会释放锁,唤醒后需要重新获取锁才能执行;状态切换到阻塞和可运行都会产生上下文切换,但是切换到可运行时候还有一锁池状态,必须要获取到锁后才会切换到可运行状态;多用于线程间的通信,如果是多线程编程建议使用该方法,减少cpu上下文的切换;这是Object的方法,必需要获取到锁才可以执行。
notify():唤醒等待队列最前面的一个线程,该方法不会立即唤醒线程,而是需要等方法体执行完了才会执行唤醒动作;这是Object的方法,必需要获取到锁才可以执行。
notifyAll():唤醒等待队列里面所有的线程,该方法不会立即唤醒线程,而是需要等方法体执行完了才会执行唤醒动作;这是Object的方法,必需要获取到锁就才可以执行。
join():线程插队,当t1线程在执行的过程中调用了t2线程的join()方法,那么t1线程会挂起(内部调用的是wait/notify机制),t2线程会立即执行。
java线程分类
java线程分为守护线程和非守护线程。
- 守护线程:和主线程一起结束的线程,叫守护线程,我们的垃圾回收线程就是一个守护线程。
- 非守护线程:主线程的结束不影响线程的执行的线程,也叫用户线程。
注意:
- 调用
t.setDaemon(true)
方法可以将用户线程设置成守护线程。 - 守护线程结束时不能保证finally语句块一定执行。
/**
* 守护线程
*
* @author yuhao.wang3
*/
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ " 任务执行 " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
Thread.sleep(50);
}
System.out.println(Thread.currentThread().getName()
+ " 任务中断 " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
System.out.println("...........finally");
}
});
thread.setDaemon(true);
thread.start();
Thread.sleep(500);
}
}
输出结果:
"C:\Program Files\Java\jdk1.8.0_112\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53547,suspend=y,server=n -Dvisualvm.id=210666216419015 -javaagent:C:\Users\yuhao.wang3\.IntelliJIdea2018.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_112\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\rt.jar;D:\workspace\spring-boot-student\spring-boot-student-concurrent\target\classes;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-web\1.5.13.RELEASE\spring-boot-starter-web-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\1.5.13.RELEASE\spring-boot-starter-tomcat-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\8.5.31\tomcat-embed-core-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\tomcat-annotations-api\8.5.31\tomcat-annotations-api-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\8.5.31\tomcat-embed-el-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.31\tomcat-embed-websocket-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\hibernate\hibernate-validator\5.3.6.Final\hibernate-validator-5.3.6.Final.jar;C:\Users\yuhao.wang3\.m2\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;C:\Users\yuhao.wang3\.m2\repository\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.8.11.1\jackson-databind-2.8.11.1.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.8.11\jackson-core-2.8.11.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-web\4.3.17.RELEASE\spring-web-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-aop\4.3.17.RELEASE\spring-aop-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-beans\4.3.17.RELEASE\spring-beans-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-context\4.3.17.RELEASE\spring-context-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-webmvc\4.3.17.RELEASE\spring-webmvc-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-expression\4.3.17.RELEASE\spring-expression-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter\1.5.13.RELEASE\spring-boot-starter-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot\1.5.13.RELEASE\spring-boot-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\1.5.13.RELEASE\spring-boot-autoconfigure-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-logging\1.5.13.RELEASE\spring-boot-starter-logging-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;C:\Users\yuhao.wang3\.m2\repository\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-core\4.3.17.RELEASE\spring-core-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\com\h2database\h2\1.4.197\h2-1.4.197.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar" com.xiaolyuh.DaemonThread
Connected to the target VM, address: '127.0.0.1:53547', transport: 'socket'
Thread-0 任务执行 2019-03-20 20:39:43_667
Thread-0 任务执行 2019-03-20 20:39:43_731
Thread-0 任务执行 2019-03-20 20:39:43_781
Thread-0 任务执行 2019-03-20 20:39:43_832
Thread-0 任务执行 2019-03-20 20:39:43_883
Thread-0 任务执行 2019-03-20 20:39:43_934
Thread-0 任务执行 2019-03-20 20:39:43_985
Thread-0 任务执行 2019-03-20 20:39:44_036
Thread-0 任务执行 2019-03-20 20:39:44_089
Thread-0 任务执行 2019-03-20 20:39:44_140
Disconnected from the target VM, address: '127.0.0.1:53547', transport: 'socket'
Process finished with exit code 0
我们可以发现finally 语句块就没执行。当thread.setDaemon(true);
这句去掉时,即使主线程结果了,子线程也会一直运行下去。
源码
https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases
spring-boot-student-concurrent 工程
layering-cache
为监控而生的多级缓存框架 layering-cache
这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下