方法简介
方法名 | 功能 | 说明 |
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的使用 | 主要是为了测试和调试 |
start 和 run
结论
如果在主线程中 直接调用run方法,这样其实并没有启动新线程,还是在用main线程来执行,并不能达到异步这样的效果。
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码。
因此,启动一个线程必须用 start方法,再用新的线程去调用run方法。
示例:直接调用 run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
输出结果
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
可以看到,没有产生新线程,都是主线程,方法调用还是同步的,必须等 FileReader.read()方法执行完才能往下做“do other things ...”
调用 start
将上面的 t1.run() 改成 t1.start()
输出
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
可以看到,产生了新线程t1,方法的调用是异步的,不用等FileReader.read()方法执行完,也能往下做“do other things ...”
sleep 与 yield
sleep
静态方法:
Thread.Sleep(1000);
//静态方法,让当前正在执行的线程进入休眠(暂时停止执行)指定的毫秒数。
作用:
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
注意:
在哪个线程里,就让那个线程睡眠,main方法中的thread.sleep()并不是使我们的子线程进入休眠,而是使我们的主线程进入休眠,因为sleep()方法是使当前线程进入休眠
睡眠结束后的线程未必会立刻得到执行
建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性,下面代码都是睡眠1秒,但是TimeUnit可读性更高,里面可以选时间的单位,而sleep就是用毫秒。
public static void main(String[] args) {
Thread.sleep(1000);
TimeUnit.SECONDS.sleep(1);
}
打断睡眠:
其它线程可以使用 interrupt 方法打断正在睡眠的线程,将其叫醒,这时 sleep 方法会抛出 InterruptedException
yield
英文意思是:让出、让步
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
具体的实现依赖于操作系统的任务调度器,如果没有其他线程,CPU空闲时,那么任务调度器会让其执行,也就是你别谦让了,没有其他人了,你来吧。
异同
都是让线程先不占用cpu
sleep是进入到 阻塞状态,yield是进入到 就绪状态,就绪状态的线程是可以被任务调度器调用执行的,但是阻塞状态的不可以。
sleep有时间参数,用于睡眠多少毫秒。yield没有时间参数
sleep应用:提高CPU效率
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让出 cpu的使用权给其他程序
例如下面的代码,通常有些服务器端会有while (true)这样的循环来一直执行,接收请求并响应,但是如果一直while (true),那么CPU会一直执行它,导致大部分时间都是空转,所以加一个睡眠,不用睡眠太久,就可以让CPU的执行效率提高很多。
while (true){
try {
Thread.sleep(50);
}catch (InterruptedException e){
e.printStackTrace();
}
}
join
作用
t1.join();
等待t1线程运行结束
为什么需要 join?
下面的代码执行,打印 r 是什么?
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
//t1 线程修改 静态变量r的值
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
//t1 线程启动
t1.start();
//主线程进行打印
log.debug("结果为:{}", r);
log.debug("结束");
}
分析
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
如何让主线程打印出最新的,也就是经过t1线程修改完毕后的r的值呢?
让主线程也进行睡眠?让主线程睡眠的时间久一点?这样显然不好,因为不好控制睡眠时间,而且t1线程睡眠完毕后不一定会被任务调度器马上调用,所以情况很复杂。
这时候就可以用 join解决这个问题:只需要在 t1.start(); 后面加上一个 t1.join(); 即可,这样主线程会等待t1线程执行完毕再执行。
有时效的 join
t1.join(1500);
interrupt
作用
可以打断正在运行的线程,也可以打断正在阻塞的线程。
- 如果打断正在阻塞中的线程,那么会让线程进入阻塞状态
- 如果打断正在运行的线程,那么就会打断之,但实际上不是强行打断,而是告诉其他线程:我要打断你,由其他线程决定自己是否要结束。
打断正在阻塞中的线程
打断 sleep,wait,join 的线程
这几个方法都会让线程进入阻塞状态
打断 sleep 的线程, 会清空打断状态,也就是打断状态变成false
以 sleep 为例
private static void test1() throws InterruptedException {
Thread t1 = new Thread(()->{
sleep(1);
}, "t1");
t1.start();
sleep(0.5);//主线程先小等一会,让t1线程进入睡眠
t1.interrupt();//主线程打断t1线程
log.debug(" 打断状态: {}", t1.isInterrupted());//输出打断标记
}
输出
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt - 打断状态: false
打断正常运行的线程
打断正常运行的线程, 不会清空打断状态,也就是打断状态会变成true
private static void test2() throws InterruptedException {
Thread t2 = new Thread(()->{
while(true) {
Thread current = Thread.currentThread();
boolean interrupted = current.isInterrupted();
if(interrupted) {
log.debug(" 打断状态: {}", interrupted);
break;
}
}
}, "t2");
t2.start();
sleep(0.5);
t2.interrupt();
}
输出
20:57:37.964 [t2] c.TestInterrupt - 打断状态: true
如果只写一个 while true ,里面没有判断的话,那么这个主线程是打断不了t2线程的。
因为interrupt实际上不能打断另一个线程,只是告诉那个线程:我要打断你,被打断的线程自己决定受不受其他线程打断。也就是将打断状态改成true,说明有其他线程要打断我。
所以可以在里面加一个if判断,如果interrupted是真,说明有其他线程要打断我,所以我自己结束好了。
打断park线程
打断 park 线程, 不会清空打断状态,也就是打断状态是true
例如下面的代码,主线程休眠之后,打断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(0.5);
t1.interrupt();
}
输出
21:11:52.795 [t1] c.TestInterrupt - park...
21:11:53.295 [t1] c.TestInterrupt - unpark...
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
private static void test4() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}
});
t1.start();
sleep(1);
t1.interrupt();
}
输出
21:13:48.783 [Thread-0] c.TestInterrupt - park...
21:13:49.809 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.812 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
21:13:49.813 [Thread-0] c.TestInterrupt - park...
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
清除打断标记
可以使用 Thread.interrupted() 清除打断状态,清除打断状态就是让打断状态变成false,意思是这个线程没有被打断
不推荐使用的方法
这些方法已过时,容易破坏同步代码块,造成线程死锁
jdk源码中也有说,不推荐这些方法
- stop():停止线程运行
- suspend():挂起(暂停)线程运行
- resume() :恢复线程运行
守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。
有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
守护线程的例子
- java的垃圾回收器线程就是一种守护线程。
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
将某个线程设置为守护线程的方法
thread.setDaemon(true);
例
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
输出
08:26:38.123 [main] c.TestDaemon - 开始运行...
08:26:38.213 [daemon] c.TestDaemon - 开始运行...
08:26:39.215 [main] c.TestDaemon - 运行结束...