多线程应用
异步调用
以调用方角度来讲,如果
+ 需要等待结果返回,才能继续运行就是同步
+ 不需要等待结果返回,就能继续运行就是异步
同步在多线程中还有另外一个意思,是让多个线程步调一致。
异步可以防止程序阻塞,例如IO操作可以处理为异步的。
提高效率
充分利用多核cpu的优势,提高运行效率。
例外:不是所有计算任务都能拆分,可能运行效率也不会提高。
Java线程
创建和运行线程
直接使用Thread
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
使用Runnable配合Thread
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread(runnable);
// 启动线程
t.start();
Java 8 以后可以使用 lambda 精简代码
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
或
Thread t2 = new Thread(()->log.debug("hello"), "t2");
t2.start();
Thread与Runnable的关系
在Thread源码中,Runnable
被赋给成员变量target
,在run()
中调用target.run()
(组合关系),而直接重写run()
就会覆盖掉Thread的run()
(继承关系)。
- 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
- 用 Runnable 更容易与线程池等高级 API 配合
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
FutureTask配合Thread
FutureTask实现了Runnable接口。
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况。Callable是带返回值类型版本的Runnable。
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
linux下对进程线程的操作
ps -fe
查看所有进程ps -fT -p <PID>
查看某个进程(PID)的所有线程kill
杀死进程top
按大写H切换是否显示线程top -H -p <PID>
查看某个进程(PID)的所有线程
Thread常用方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线程,在新的线程运行 run 方法中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException | |
run() | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为 | |
join() | 等待线程运行结束(进程间通信) | ||
join(long n) | 等待线程运行结束,最多等待n毫秒 | ||
getId() | 获取线程长整型的 id | id 唯一 | |
getName() | 获取线程名 | ||
setName(String) | 修改线程名 | ||
getPriority() | 获取线程优先级 | ||
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率 | |
getState() | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为:NEW , RUNNABLE , BLOCKED , WAITING,TIMED_WAITING , TERMINATED |
|
isInterrupted() | 判断是否被打断 | 不会清除打断标记 |
|
isAlive() | 线程是否存活(还没有运行完毕) | ||
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置打断标记 ;park 的线程被打断,也会设置打断标记 |
|
interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程 | |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
currentThread() | static | 获取当前线程对象 |
sleep与yield
sleep
- 调用 sleep 会让当前线程从
Running
进入Timed Waiting
状态(阻塞) - 其它线程可以使用
interrupt
方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
TimeUnit.SECONDS.sleep(1);
TimeUnit.MILLISECONDS.sleep(1)
yield
- 调用 yield 会让当前线程从
Running
进入Runnable
就绪状态,然后调度执行其它线程(仍有可能立即被调度) - 具体的实现依赖于操作系统的任务调度器
sleep应用
在没有利用cpu来计算时,不要让while(true)
空转浪费cpu,这时可以使用yield或sleep来让出cpu的使用权给其他程序
while(true){
try{
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
}
- 可以用wait或条件变量达到类似的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无需锁同步的场景
join方法
join方法的作用
下面这段代码,线程t1运行改变r的值需要1秒的时间,主线程就获取不了r被改变后的值,可以使用join解决。
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
//使用join可以等待t1运行结束
//t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
这种应用就是同步机制。
有时效的join
join(long millis)
如果在限定的时间内线程没有结束,则当前线程停止等待,继续执行。
join的底层实现是wait
interrupt方法
只是置一个标志位,是否停止运行由线程自己决定。
两阶段终止模式

class TPTInterrupt {
private Thread thread;
public void start(){
thread = new Thread(() -> {
while(true) {
Thread current = Thread.currentThread();
if(current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
current.interrupt();
}
// 执行监控操作
}
},"监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
打断park线程
打断 park 线程, 不会清空打断状态
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
sleep(1);
t1.interrupt();
}
不推荐使用的方法
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
五种状态
这是从 操作系统 层面来描述的

- 【初始状态】(新建状态)仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
六种状态
这是从 Java API 层面来描述的
根据 Thread.State 枚举,分为六种状态:

NEW
线程刚被创建,但是还没有调用start()
方法RUNNABLE
当调用了start()
方法之后,注意,Java API 层面的RUNNABLE
状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行)BLOCKED
,WAITING
,TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分TERMINATED
当线程代码运行结束
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/java%e5%a4%9a%e7%ba%bf%e7%a8%8b%e5%9f%ba%e7%a1%80/