一、start()与run()
好多面试官也会作为一道面试题,问你start()与run()方法的区别是什么?接下来给大家讲解一下。
其实很好理解,run()方法是类中的一个普通方法,当我们使用Thread类或者Runnable接口时,会重写此方法。如果我们直接调用run()方法,如下:
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
log.debug("running...");
}
},"t1");
t1.run();
}
打印结果:
22:31:50.919 DEBUG [main] c.Test1 - running...
我们可以看见,执行run()方法的其实是main线程,并没有使用新创建的t1线程去执行。
如果调用start()方法:
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
log.debug("running...");
}
},"t1");
t1.start();
}
打印结果:
22:34:41.522 DEBUG [t1] c.Test1 - running...
我们可以看见,此时执行run()方法中内容的是t1线程。
二者区别:
run()方法是线程类内部的一个普通方法,直接调用并不会异步执行、不会提高性能。
start()方法是用来启动线程的方法,调用此方法后线程会进入就绪状态等待任务调度器调度执行,可以异步执行方法,提高性能。
二、sleep()和yield()
sleep()方法---线程休眠(调用此方法的线程状态从RUNNABLE状态变成TIMED_WAITING)
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
try {
log.debug("t1进入休眠状态...");
Thread.sleep(10000);
log.debug("t1休眠结束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
log.debug("t1 当前的状态是:"+t1.getState());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 当前的状态是:"+t1.getState());
}
打印结果:
22:52:07.902 DEBUG [main] c.Test2 - t1 当前的状态是:RUNNABLE
22:52:07.902 DEBUG [t1] c.Test2 - t1进入休眠状态...
22:52:12.903 DEBUG [main] c.Test2 - t1 当前的状态是:TIMED_WAITING
22:52:17.903 DEBUG [t1] c.Test2 - t1休眠结束...
sleep的可读性-TimeUnit(java.util.concurrent)在jdk1.5之后
//线程休眠5秒
Thread.sleep(5000);
//使用TimeUnit让线程休眠5秒,可以指定时间单位,增加代码的可读性
TimeUnit.SECONDS.sleep(5);
其实TimeUnit内部调用的也是Thread.sleep()方法,只是内部做了转换。源码如下:
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
yield()方法---让出线程 调用此方法的线程从运行状态变成就绪状态,等待cpu任务调度器再次调度执行。
二者区别:
sleep()方法--线程会进入阻塞状态(TIME_WAITING),任务调度器不会调用阻塞状态的线程,直到指定的休眠时间到了才会继续执行。
yield()方法-- 线程会重新变成就绪状态,任务调度器会再次调度执行。
sleep()方法的应用:防止CPU占用率100%。
三、join()方法
先看一个例子:
static int count = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
log.debug("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 10;
log.debug("end...");
},"t1");
t1.start();
log.debug(String.valueOf(count));
}
输出结果:
22:52:22.261 DEBUG [main] c.Test5 - 0
22:52:22.261 DEBUG [t1] c.Test5 - start...
22:52:23.263 DEBUG [t1] c.Test5 - end...
有人可能有疑问,为什么输出的不是10而是0?t1线程中明明给count赋值为10了啊!
因为此处t1线程是不同于main线程而格外创建的线程,两条线程互不干扰只会执行自己手中的任务。当main线程创建并启动t1线程后,并不会主动等待t1线程执行结束后才会继续往下执行,而是直接往下执行,所以就会看到当前的输出结果。
可是有时候我就需要等待t1线程执行结束后,main线程才能继续往下运行怎么办?那么就是保证线程的同步,可以使用join()方法。
join()-- 等待调用线程执行结束
static int count = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
log.debug("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 10;
log.debug("end...");
},"t1");
t1.start();
//在获取结果之前,等待t1执行结束
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(String.valueOf(count));
}
输出结果:
22:57:23.373 DEBUG [t1] c.Test5 - start...
22:57:24.374 DEBUG [t1] c.Test5 - end...
22:57:24.374 DEBUG [main] c.Test5 - 10
四、interrupt()方法
1.用于打断阻塞状态的线程,打断后会有打断标记,但是会重置标记为false。线程什么时候会进入阻塞状态?(当线程调用sleep(),wait(),join())
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() ->{
log.debug("{}","sleep...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
log.debug("{}","被打断...");
}
},"t1");
t1.start();
/**
* 此处如果主线程不进入阻塞状态,那么t1线程还没进入阻塞状态,此时打断方法则为打断运行中的线程。
*
* 如果打断的是运行中的线程,打断标记不会被清除,调用其isInterrupted()方法返回的是true,表示该线程被打断了。
* 如果打断的是阻塞中的线程,打断标记会被重置,调用其isInterrupted()方法返回的是false,表示该线程被打断了,但是标记被清除了。
*/
Thread.sleep(200);
t1.interrupt();
log.debug("t1线程是否被打断:{}",t1.isInterrupted());
}
输出结果:
21:31:18.131 DEBUG [t1] c.Test7 - sleep...
21:31:18.331 DEBUG [t1] c.Test7 - 被打断...
21:31:18.331 DEBUG [main] c.Test7 - t1线程是否被打断:false
2.用于打断正在执行的线程,打断后会有打断标记,如果被打断过,标记则为true。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() ->{
while (Boolean.TRUE){
boolean flag = Thread.currentThread().isInterrupted();
if (flag){
break;
}
}
});
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
}
总结:interrupt()方法不会真正终止线程,而是给被打断的线程做一个标记。我们可以根据这个标记来区分是否打断。
两阶段终止模式
Two Phase Termination
在一个线程T1中如何优雅的终止线程T2?这里的优雅指的是给T2线程一个料理后事的机会。
错误思路
-
使用线程对象的stop()方法来停止
问题:stop()方法会真正杀死线程
-
使用System.exit(int)方法来停止
问题:小题大作,我们仅仅是为了停止一个线程,这种做法会让整个程序都停止。
public static void main(String[] args) throws InterruptedException {
//创建监控线程
Thread monitor = new Thread(() ->{
while (true){
Thread t1 = Thread.currentThread();
if (t1.isInterrupted()){
log.debug("{}","料理后事...");
break;
}
try {
TimeUnit.SECONDS.sleep(2);
log.debug("{}","执行监控记录");
} catch (InterruptedException e) {
//此处再次打断是为了解决监控线程在睡眠中被打断,标记会被重置的问题。
t1.interrupt();
}
}
},"monitor");
monitor.start();
//主线程隔5s后打断监控线程
Thread.sleep(5000);
monitor.interrupt();
}
3.用于打断park()线程
如果一个线程调用了park()方法,会进入阻塞状态不会继续往下执行
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
log.debug("{}","park...");
//如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
LockSupport.park();
log.debug("{}","unPark...");
},"t1");
t1.start();
}
输出结果:
11:01:17.083 [t1] - park...
使用interrupt()打断
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() ->{
log.debug("{}","park...");
//如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
LockSupport.park();
log.debug("{}","unPark...");
log.debug("被打断,标记为{}",Thread.currentThread().isInterrupted());
},"t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
//1s后打断t1线程
t1.interrupt();
}
输出结果:
11:05:52.550 [t1] - park...
11:05:53.549 [t1] - unPark...
11:05:53.549 [t1] - 被打断,标记为true
LockSupport.park()方法有个特点,如果该线程有打断标记后,再次执行此方法会不生效,无法park住线程执行。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() ->{
log.debug("{}","park...");
//如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
LockSupport.park();
log.debug("{}","unPark...");
log.debug("被打断,标记为{}",Thread.currentThread().isInterrupted());
//已经存在打断标记的线程 再次调用park()方法
LockSupport.park();
log.debug("{}","unPark...");
},"t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
//1s后打断t1线程
t1.interrupt();
}
输出结果:
11:09:07.548 [t1] - park...
11:09:08.548 [t1] - unPark...
11:09:08.548 [t1] - 被打断,标记为true
11:09:08.548 [t1] - unPark...
我们可以看见,unPark打印了两次。如何解决?线程的方法中有一个interrupted()方法也是判断线程是否存在打断标记,但是返回结果后会将该线程的打断标记清除掉。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() ->{
log.debug("{}","park...");
//如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
LockSupport.park();
log.debug("{}","unPark...");
//此处获取线程打断标记使用interrupted()方法,返回结果后会清除打断标记
log.debug("被打断,标记为{}",Thread.interrupted());
//已经存在打断标记的线程 再次调用park()方法
LockSupport.park();
log.debug("{}","unPark...");
},"t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
//1s后打断t1线程
t1.interrupt();
}
输出结果:
11:11:46.529 [t1] - park...
11:11:47.528 [t1] - unPark...
11:11:47.528 [t1] - 被打断,标记为true
五、不推荐使用的方法
- stop()-停止
- suspend()-挂起
- resume()-恢复
以上方法jdk官网也不推荐使用,已经被标记为过期方法。上述方法会影响到同步代码块,引起线程获取锁后不会释放锁的问题,破坏代码的安全性。
六、守护线程
先看一下这段代码,当主线程执行结束后,程序并不会结束,因为t1线程还在运行中。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
if (Thread.currentThread().isInterrupted()){
break;
}
}
log.debug("{}","over...");
},"t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("{}","over...");
}
把t1线程设为守护线程后,我们发现当主线程结束后,t1也不会继续执行死循环了,整个程序会结束。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
if (Thread.currentThread().isInterrupted()){
break;
}
}
log.debug("{}","over...");
},"t1");
//将t1设为守护线程
t1.setDaemon(true);
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("{}","over...");
}