查看线程基本信息
public class ThreadDemo01 {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println("当前线程名称: " + thread.getName());
System.out.println("当前线程ID: " + thread.getId());
System.out.println("当前线程组名称: " + thread.getThreadGroup().getName());
System.out.println("当前线程状态: " + thread.getState());
System.out.println("当前线程优先级: " + thread.getPriority());
fun1();
}
private static void fun1() {
fun2();
}
private static void fun2() {
int i, j = 0;
System.out.println();
}
}
## 输出
当前线程名称: main
当前线程ID: 1
当前线程组名称: main
当前线程状态: RUNNABLE
当前线程优先级: 5
先看线程栈帧信息, 把debug端点打在fun2方法中. 查看栈帧

可以在每个线程栈帧中查看帧中的局部变量信息. 可看到这里有三个帧, 分别是代表了三个方法.
什么是线程
CPU调度的最小单位, 进程是操作系统分配资源的最小单位.
一个进程最少包含一个线程或者拥有多个线程.
各个进程之间是互相独立的, 同一个进程内的不同线程之间可以有关联. 且可以共享进程下的资源信息.
其他结论
同一时刻, 一个cpu内核上只会有一个线程在执行.
在java中, 每个线程都对应一个Thread对象实例. 线程的具体描述信息都保存在Therad类实例的属性中.
Java中三个创建线程的方法
在Java中有三种创建线程的方式, 但是每种方式都和Thread类有关系.
Thread类详解
- 线程id
属性定义: private final long tid;
获取方法: getId()
- 线程名称
private volatile String name;
getName();
- 线程优先级
private int priority;
getPriority()
- 是否为守护线程
private boolean daemon = false;
isDaemon();
- 线程状态
private volatile int threadStatus;
public State getState() {
// get current thread state
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
- 线程的启动和运行
public synchronized void start();
start方法会调用Thread实例的run方法, run()方法作为用户代码逻辑的入口, 由start方法调用.
具体的运行时机, 由操作系统分配.
public void run() {
if (target != null) {
target.run();
}
}
target属性是当前实例的一个重要实例属性. 默认这个实例属性为null, 所以直接new Thread, 并调用其start方法, 什么也没有执行, 就是因为这个target属性为null;
- 获取当前线程
Thread.currentThread();
继承Thread类创建线程
public class MyThread01 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
MyThread01类的实例的start方法, 就会执行这里重写的run方法, 作为用户逻辑的入口方法.
优点: 方便快捷
缺点: java单继承属性, 不方便扩展.
实现Runnable接口创建线程目标类
public class MyRunnable01 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Thread类也实现了Runnable接口, 并且实现了其run方法, 并且在run方法内执行了target实例属性的run方法. 默认这个target实例属性为null.
什么时候这个target属性不为空?
Thread类有多个构造方法可以初始化该属性
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
this(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
this(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
this(group, target, name, stackSize, null, true);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize, boolean inheritThreadLocals) {
this(group, target, name, stackSize, null, inheritThreadLocals);
}
所以可以通过自定义实现类实现Runnable接口, 并重写run方法. 然后通过Thread类的构造方法来创建一个线程
Thread thread = new Thread(new MyRunnable01());
两种更优雅的方式通过Runnable接口创建线程
- 匿名类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
- Lambda
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
这种使用方式需要目标接口Runnable是一个函数式接口, 即只有一个抽象方法.
优点: 逻辑和数据的分离, 可以更方便复用. 解决了单继承的缺陷
缺点: 没返回值.
Callable和FutureTask
private static void thread3() throws ExecutionException, InterruptedException {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return "hello thread";
}
};
FutureTask<String> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
System.out.println(task.get());
}
通过实现Callable接口, 创建一个实现类.
将实现类传递给FutureTask的构造方法. 构造FutureTask实例,
再把FutureTask实例传递给Thread构造函数.
其中FutureTask就是一个中间类, 将Callable和Runnable接口关联起来, 典型的设计模式中的适配器模式. 返回值通过FutureTask实例中的实例属性: outcome返回.
通过线程池创建
private static void thread4() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(() -> System.out.println("今天天气不错"));
Future<String> submit = executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("今天阳光明媚");
}
}, "hello");
System.out.println(submit.get());
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "hello submit callable";
}
});
System.out.println(future.get());
Future<String> call = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "hello world";
}
});
System.out.println(call.get());
executorService.shutdown();
}
在线程池中提交任务有三种方式
- result = submit(Runnable, result)
- result = submit(Callable)
- result = void submit(Runnable)
- execute()
线程原理
线程的调度和时间片
由于cpu的速度很快, 每秒能够执行十亿次计算, 所以把cpu的计算按照毫秒分开, 例如20ms为一个时间片, 不同的操作系统的时间片长度是不一样的.
线程的调度就是基于时间片来调度的, 只有被分配到时间片的线程, 才能够得到执行, 否则就是就绪状态, 由于速度太快, 在多个线程之间切换时间片对于用户来说是无感知的, 感觉就像是在并发执行.
时间片的分配策略:
- 分时调度模型, 线程轮流执行, 每个线程占用的时间片都是相等的.
- 抢占式调度, 线程区分优先级, 优先级高的线程能够获得更多的时间片. (主流操作系统采取的方式)
java的线程操作是委托给操作系统的, 所以java的线程调度模型也是抢占式的.
线程的优先级
在Thread类中提供了两个方法用来操作线程的优先级, priority, get set
优先级越高的线程获得cpu时间片的机会越高, 但这不是绝对的. 不能依赖此来控制线程的执行顺序.
线程的生命周期
6中状态:
- NEW
线程对象创建好, 还没有调用其start方法.
- RUNNABLE
创建好的线程在调用start方法后, 如果被分配到cpu时间片, 就处于运行状态, 如果时间片用完, 等待再次分配时间片, 在这种执行中和等待时间片的状态称为: runnable
- BLOCK
阻塞状态, 等待被唤醒.
- TIMED_WITTING
线程阻塞指定的时间段, 时间结束后进入runnable状态.
- WITTING
- TERMINATED
线程执行结束
代码演示线程的不同状态
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 100000000; i++) {
}
LockSupport.parkNanos(1000 * 1000 * 100);
}
};
System.out.println("new Thread 之后的线程状态: " + thread.getState());
thread.start();
System.out.println("thread.start() 之后的线程状态: " + thread.getState());
LockSupport.parkNanos(1000 * 1000 * 10);
System.out.println("LockSupport.park() 之后的线程状态: " + thread.getState());
thread.join();
System.out.println("thread执行结束之后的线程状态: " + thread.getState());
}
## 输出log
new Thread 之后的线程状态: NEW
thread.start() 之后的线程状态: RUNNABLE
LockSupport.park() 之后的线程状态: TIMED_WAITING
thread执行结束之后的线程状态: TERMINATED
线程状态查看
jps查看java进程

jstack查看状态

线程的基本操作
- 名称的设置和获取
getName(); setName();
- 线程的sleep操作
Thread.sleep(1000);
调用后, 线程会处于TIMED_WAITING状态.
- interrupt操作
线程终止操作: stop, 已经过时, 不安全, 可能造成数据不一致问题, 锁无法释放问题等等.
Thread.interrupt()方法, 本质不是用来中断一个线程, 而是把线程设置为中断状态. 调用iterrupt方法的作用:
- 如果线程处于阻塞状态(例如调用了线程的Object.wait(), Thread.join(), Thread.sleep()方法), 会立马退出阻塞, 并抛出InterruptedException异常. 线程捕获这个异常, 并做一定的处理, 然后让线程安全的退出.
private static void testInBlock() throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(LocalDateTime.now());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread.sleep(2000);
thread.interrupt();
thread.join();
System.out.println(LocalDateTime.now());
}
## 输出
2021-12-06T16:28:25.554670
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at main.java.com.lee.thread.InterruptTest$2.run(InterruptTest.java:52)
2021-12-06T16:28:27.536343
可以看到在thread线程中, sleep的参数为5s, 在主线程sleep 2s后调用了thread的interrupt方法, 导致只sleep了2s,从25→27. 后sleep状态被强行中断.
- 如果线程处于运行中. 线程不会受影响, 继续运行, 仅仅是线程的中断标记被设置为true. 如果需要响应中断, 需要在线程适当的位置查看当前线程的中断标识, 并确定是否执行中断操作.
private static void inRunning() throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("运行中");
}
};
thread.start();
System.out.println(thread.isInterrupted());
thread.interrupt();
System.out.println(thread.isInterrupted());
thread.join();
}
## 输出
false
运行中
true
可见对于运行中的线程, 调用线程的interrupt方法, 不会对线程造成任何影响, 只是将thread的interrupted属性设置为true;
- 如果先调用interrupt()方法, 后调用阻塞方法来阻塞线程,
在第一次调用阻塞方法进入阻塞状态时, 依旧会抛出InterruptedException, 后续再调用阻塞方法, 就不会再触发InterruptedException.
- join 线程合并
线程A在执行到某个位置的时候, 需要等待线程B先执行完成, 可以在线程A中需要的位置调用方法threadB.join(); 此时线程A就会处于阻塞状态, 直到线程B执行完成来唤醒线程A, join()方法有包含时间参数的重载版本, 表示可以阻塞一定的时间后自动恢复执行状态. 如果线程A被中断, 会抛出InterruptedException异常.
join()方法是实例方法, 需要使用被合并线程的句柄(指针, 变量)去调用.
join()方法无法直接获取被依赖线程的执行结果.
直接调用join()方法, 会让依赖线程进入waitting状态, cpu不会分配资源给该线程, object的wait方法也是 一样的效果.
通过join(xxxx)的重载版本, 会让线程进入timed_waitting状态, 等待时间结束后被唤醒, 或者主动被其他线程唤醒.
- yield操作 让步
让正在执行的线程, 放弃持有的cpu执行权限, 线程重新进入runnable状态, 有可能当前线程会再次竞争到cpu资源得到执行.
yield只会让线程从运行状态切换到就绪状态, 不会进入阻塞状态.
不能保证是的当前正在运行的线程迅速转换为就绪状态.
即使迅速的转换到了就绪状态, 下一次的cpu切换还是有可能选中当前线程.
所以通过yield方法严格的控制线程的执行顺序.
- daemon操作 守护线程.
例如随着jvm启动的gc线程. 守护线程随着jvm的结束才会结束.
可以通过setDaemon来设置一个线程为守护线程.
- 守护线程一定要在启动之前设置为守护线程. 如果启动后设置会抛出interruptedException异常.
- 守护线程随时可能被jvm强行终止, 不适合用于访问系统资源等操作, 防止造成数据不一致的风险.
- 守护线程中创建一个线程, 那个这个线程也是守护线程. 可以通过setDaemon方法设置为用户线程.
- 线程状态总结
通过new Thread方式创建线程之后还未调用start方法, 线程就是处于new状态, runnable,和callable本质都是通过new thread的方式在创建线程, 只是target对象不同而已.
runnable, 在创建好的线程调用start方法后, 线程进入可执行状态, 表示线程就绪或者运行中.
就绪表示为线程具备运行条件, 但是还没有获得cpu资源. 进入就绪状态的条件包括:
- start方法
- cpu时间片用完
- sleep时间结束
- join操作结束
- 等待用户输入结束
- 线程获取到对象锁
- 调用yield方法让出时间片.
运行中: 表示线程在继续状态, 并且被分配到cpu时间片后的状态.
block状态
线程不会占用cpu资源. 进入条件:
- 等待获取对象锁
- io阻塞
waitting状态
线程在waitting状态会处于无限制的等待, 直到有其他现场显示唤醒该线程.
TimedWaitting
线程处于限时等待状态, 在限时结束或者其他线程显示唤醒该线程即可进入runnable状态. 进入方式:
sleep(time), wait(time)parkNanos(time), join(time)
terminated
线程正常结束, 或者线程异常后没有被处理进入结束状态.