Java线程
目录
创建和运行线程
使用Thread
//构造方法的参数是给线程指定名字
Thread t1 = new Thread("t1"){
@Override
public void run() {
//要执行的任务
logger.debug("hh");
}
};
//启动线程
t1.start();
输出:
14:16:23.215 [t1] DEBUG com.fly.n1.Test1 - hh
使用 Runnable 配合 Thread
- Thread 代表线程
- Runnable 可运行的任务(线程要执行的代码)
Runnable runnable = () -> {
//要执行的任务
logger.debug("Runnable");
};
//创建线程对象
Thread t2 = new Thread(runnable,"t2");
//启动线程
t2.start();
输出:
14:31:02.613 [t2] DEBUG com.fly.n1.Test1 - Runnable
Thread 与 Runnable 的关系
小结:
- 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
- 用 Runnable 更容易与线程池等高级 API 配合
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
FutureTask 配合 Thread
//创建任务对象
FutureTask<Integer> task = new FutureTask<>(() -> {
logger.debug("task");
return 10;
});
Thread t3 = new Thread(task, "t3");
t3.start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task.get();
logger.debug("结果是:{}",result);
输出:
14:48:33.946 [t3] DEBUG com.fly.n1.Test1 - task
14:48:33.951 [main] DEBUG com.fly.n1.Test1 - 结果是:10
查看进程线程的方法
Windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程
- taskkill 杀死进程
Linux
- ps -fe 查看所有进程
- ps -fT -p
查看某个进程(PID)的所有线程 - kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p
查看某个进程(PID)的所有线程
Java
- jps 命令查看所有 Java 进程
- jstack
查看某个 Java 进程(PID)的所有线程状态 - jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
线程运行原理
- 每个线程启动后,虚拟机就会为其分配一块栈内存
1.1 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
1.2 每个线程只能有一个活动栈帧,对应着当前正在执行的方法 - 因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
2.1 线程的 cpu 时间片用完
2.2 垃圾回收
2.3 有更高优先级的线程需要运行
2.4 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法 - 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
start 与 run
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
sleep 与 yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable
- 具体的实现依赖于操作系统的任务调度器
线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
join
等待这个线程结束
private static int num = 10;
private static void method4() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 20;
}, "t1");
t1.start();
t1.join();
num++;
logger.debug("结果:{}",num);
}
输出:
18:24:19.095 [main] DEBUG com.fly.n1.Test1 - 结果:21
interrupt
- 打断 sleep,wait,join 的线程.这几个方法都会让线程进入阻塞状态,打断 sleep 的线程, 会清空打断状态
- 打断正常运行的线程, 不会清空打断状态
两阶段终止模式
class TPTInterrupt {
private Thread thread;
private static final Logger log = LoggerFactory.getLogger(Test2.class);
public void start() {
thread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) {
log.debug("后续操作...");
break;
}
try {
Thread.sleep(1000);//此时被打断进入catch块,会把interrupt标记置为 false
log.debug("监控记录...");//正常流程
} catch (InterruptedException e) {
current.interrupt();
}
}
},"监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(2500);
t.stop();
输出:
18:00:23.033 [监控线程] DEBUG com.fly.n1.Test2 - 保存结果...
18:00:24.037 [监控线程] DEBUG com.fly.n1.Test2 - 保存结果...
18:00:24.531 [监控线程] DEBUG com.fly.n1.Test2 - 保存结果...
18:00:24.531 [监控线程] DEBUG com.fly.n1.Test2 - 后续操作...
打断park线程
打断 park 线程, 不会清空打断状态,如果打断标记已经是 true, 则 park 会失效
输出:
19:24:59.188 [t1] DEBUG com.fly.n1.Test3 - park...
19:24:59.685 [t1] DEBUG com.fly.n1.Test3 - unpark...
19:24:59.685 [t1] DEBUG com.fly.n1.Test3 - 打断状态:true
19:24:59.688 [t1] DEBUG com.fly.n1.Test3 - unpark...
主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
private static final Logger logger = LoggerFactory.getLogger(Test4.class);
public static void main(String[] args) throws InterruptedException {
logger.debug("开始运行");
Thread t1 = new Thread(() -> {
logger.debug("开始运行");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.debug("运行结束");
}, "daemon");
//设置该线程为守护线程
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
logger.debug("运行结束");
}
输出:
19:38:15.830 [main] DEBUG com.fly.n1.Test4 - 开始运行
19:38:15.836 [daemon] DEBUG com.fly.n1.Test4 - 开始运行
19:38:16.835 [main] DEBUG com.fly.n1.Test4 - 运行结束
线程状态
五种状态
- 这是从 操作系统 层面来描述的:初始状态、可运行状态(就绪状态)、运行状态、阻塞状态、终止状态
- 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 可运行状态:指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 阻塞状态:
4.1 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 阻塞状态
4.2 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至 可运行状态
4.3 与 可运行状态 的区别是,对 阻塞状态 的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们 - 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
六种状态
- 这是从 Java API 层面来描述的根据 Thread.State 枚举,分为六种状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
- NEW:线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE:当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 可运行状态、运行状态 和 阻塞状态(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
- BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对 阻塞状态 的细分
- TERMINATED:当线程代码运行结束
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?