- @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;
/**
-
-
线程状态示例代码
-
@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查看输出:
整理输出结果:
| 线程名称 | 线程状态 |
| — | — |
| Blocked2Thread | BLOCKED (on object monitor),阻塞在获取Blocked.class的锁上 |
| Blocked1Thread | TIMED_WAITING (sleeping) |
| WaitingThread | WAITING (on object
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
monitor) |
| TimeWaitingThread | TIMED_WAITING (sleeping) |
总结:
线程在自身生命周期中不是规定处于某一个状态,而是随着代码的执行在不同的状态之间进行切换。
Java线程的状态变化图如下:
Java线程状态变迁图
总结:
-
线程创建后,调用start()方法开始运行
-
线程执行wait()方法后,线程进入等待状态,进入等待的线程需要依靠其他线程才能够返回到运行状态
-
超时等待相当于在等待状态的基础上增加了超时限制,达到设置的超时时间后返回到运行状态
-
线程执行同步方法或代码块时,未获取到锁的线程,将会进入到阻塞状态。
-
线程执行完Runnable的run()方法之后进入到终止状态
-
阻塞在Java的concurrent包中Lock接口的线程是等待状态,因为Lock接口阻塞的实现使用的是Daemon线程
6、Daemon线程
简介:
Daemon线程是一种支持型线程,它的主要作用是程序中后台调度和支持性工作。当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。Daemon线程需要在启动之前设置,不能在启动之后设置。
设置方式:
Thread.setDaemon(true)
需要特别注意的点:
Daemon线程被用作支持性工作的完成,但是在Java虚拟机退出时Daemon线程的finally代码块不一定执行。
示例代码:
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();
}
}
输出结果:
总结:
不难发现,DaemonRunner的run方法的finally代码块并没有执行,这是因为,当Java虚拟机中已经没有非Daemon线程时,虚拟机会立即退出,虚拟机中的所以daemon线程需要立即终止,所以线程DaemonRunner会被立即终止,finally并未执行。
1、构造线程
运行线程之前需要构造一个线程对象,线程对象在构造的时候需要设置一些线程的属性,这些属性包括线程组、线程的优先级、是否是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、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时会分配一个唯一的ID来标志线程。此时一个完整的能够运行的线程对象就初始化好了,在堆内存中等待运行。
2、什么是线程中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以通过调用静态方法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);
}
}
}
}
输出结果:
总结:
上述代码执行输出的结果,与API说明和我们的预期完成一致,但是看似正确的代码却隐藏这很多问题。
存在问题:
-
suspend()方法调用后不会释放已占有的资源(比如锁),可能会导致死锁
-
stop()方法在终结一个线程时不能保证资源的正常释放,可能会导致程序处于不确定的工作状态
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;
}
}