1. 创建和运行线程
1.1 Thread
public static void main(String[] args) {
// 创建线程对象
Thread thread = new Thread(){
@Override
public void run() {
// 要执行的任务
System.out.println(Thread.currentThread().getName()+"==>"+"running");
}
};
//给线程命名
thread.setName("t1");
// 启动线程
thread.start();
System.out.println(Thread.currentThread().getName()+"==>"+"running");
}
1.2 Runnable
public static void main(String[] args) {
// 创建任务对象
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"===>"+"running");
}
};
//参数1 是任务对象; 参数2 是线程名字
Thread thread = new Thread(runnable,"t1");
thread.start();
System.out.println(Thread.currentThread().getName()+"===>"+"running");
}
Thread 和 Runnable的区别
Runnable接口是一个线程任务类最终执行还是要通过Thread类的start方法
1.3 Callable
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务对象
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"===>running");
Thread.sleep(1000);
return 100;
}
});
// 参数1 是任务对象; 参数2 是线程名字
Thread thread = new Thread(task,"t1");
thread.start();
// 主线程阻塞,同步等待 task 执行完毕的结果
System.out.println(Thread.currentThread().getName()+"===>"+task.get());
}
执行结果:
t1===>running
main===>100
2. 查看线程的方法
windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程, 可以在后面跟上| findStr 进行过滤
- taskkill 杀死进程(如: taskkill /F /PID 1002)/F 强制删除
linux
- ps -ef 查看所有进程
- ps -fT -p
,查看某个进程(PID)的所有线程 - kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p
查看某个进程(PID)的所有线程
Java
- jps 命令查看所有 Java 进程
- jstack
查看某个 Java 进程(PID)的所有线程状态 - jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
3. 线程运行原理
栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
总结:每一个线程对应一个栈内存,而线程中方法则对应栈帧。
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码:
- 线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
- 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch 频繁发生会影响性能
4 线程常见方法
4.1 start vs run
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
4.2 sleep vs yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
4.3 线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
4.4 join
阻塞等待线程执行完毕,底层是使用wait方法
4.5 interrupt
- sleep、wait、join都是阻塞状态,使用interrupt打断处于阻塞状态的线程时会使程序抛出异常并清空打断标记,也就是将打断标记置为false。
- 使用interrupt打断正常运行的线程时只会将打断标记置为true,并不会真正的停止线程,我们可以通过判断线程的isInterrupted()方法手动安全的停止线程。
4.6 结束线程之两阶段终止模式
错误思路
- 使用线程对象的 stop() 方法停止线程,stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
- 使用 System.exit(int) 方法停止线程,目的仅是停止一个线程,但这种做法会让整个程序都停止
两阶段终止模式
代码模拟:
public class Test5 {
public static void main(String[] args) throws InterruptedException {
TPTInterrupt tptInterrupt = new TPTInterrupt();
System.out.println("开启监控线程");
tptInterrupt.start();
Thread.sleep(5000);
System.out.println("关闭监控线程");
tptInterrupt.stop();
}
static class TPTInterrupt{
Thread monitor;
public void start(){
monitor = new Thread(()->{
while (true){
if(monitor.isInterrupted()){
System.out.println("料理后事");
break;
}
try {
Thread.sleep(1000); //case1: 打断休眠中的线程
System.out.println("执行监控线程"); // case2: 打断运行中的线程
} catch (InterruptedException e) {
e.printStackTrace();
monitor.interrupt();
}
}
},"监控线程");
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
}
执行结果:
开启监控线程
执行监控线程
执行监控线程
执行监控线程
执行监控线程
关闭监控线程
料理后事
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.anchor.test.Test5$TPTInterrupt.lambda$start$0(Test5.java:34)
at java.lang.Thread.run(Thread.java:750)
Process finished with exit code 0
LockSupport.park()休眠线程,LockSupport.unpark()唤醒线程,两个方法配合使用。也可以通过LockSupport.parkNanos()指定休眠时间后,自动唤醒。 LockSupport.park()不会释放monitor锁。 线程被打断,LockSupport.park()不会抛出异常,也不会吞噬掉interrupt的状态,调用者可以获取interrupt状态,自行进行判断,线程是由于什么原因被唤醒了。 LockSupport.park()会是线程进入WAITING状态,而LockSupport.parkNanos(long nanos) 会进入TIMED_WAITING状态。
如果是打断LockSupport.park(),park内部通过判断是否被打断来决定是暂停线程,使用isInterrupted()不会清除打断标记,则将无法通过park方法再次休眠线程。 可使用静态方法Thread.interruped(),此方法会清除打断标记。
4.7 主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。例如:java中的垃圾回收线程就是一种守护线程。
5 线程状态
5.1 五种状态
从操作系统层面描述的线程状态
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
5.2 六种状态
从java api 层面的描述的线程状态,主要对应Thread.State 枚举
- NEW 线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
- BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
- TERMINATED 当线程代码运行结束
用Java代码模拟线程的不同状态:
import java.io.IOException;
public class TestState {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "===>running...");
}
};
Thread t2 = new Thread("t2") {
@Override
public void run() {
while (true) { // runnable
}
}
};
t2.start();
Thread t3 = new Thread("t3") {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "===>running...");
}
};
t3.start();
Thread t4 = new Thread("t4") {
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join(); // waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class) { // blocked
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 state " + t1.getState()); //NEW
System.out.println("t2 state " + t2.getState()); // RUNNABLE
System.out.println("t3 state " + t3.getState()); // TERMINATED
System.out.println("t4 state " + t4.getState()); // TIMED_WAITING
System.out.println("t5 state " + t5.getState()); // WAITING
System.out.println("t6 state " + t6.getState()); // BLOCKED
System.in.read();
}
}
评论