目录
一、Thread类及常见方法
1. join() 等待一个线程
2. currentThread() 获取当前线程引用
3. sleep() 休眠当前线程
二、线程的状态
1. 线程的所有状态
2. 状态转移
一、Thread类及常见方法
接上文:多线程(1)http://t.csdnimg.cn/wuphT
Thread类中还有一些要介绍的方法 。
1. join() 等待一个线程
有时,我们需要等待⼀个线程完成它的工作后,才能进行下一步的工作。例如,张三只有等李四转账成功,才决定是否收钱,这时我们需要⼀个⽅法明确等待线程的结束。
public class Demo6 {
public static void main(String[] args) throws InterruptedException {
//join() 等待一个线程
Thread t = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("thread end");
});
t.start();
//死等
//t.join(); 先打印 thread end 再打印 main end
//最多等millis毫秒
t.join(1000);
System.out.println("main end"); //先打印 main end 再打印 thread end
}
}
当在一个线程中调用另一个线程的 join()
方法时,当前线程会被阻塞,直到被调用的线程执行完成或超时。例如在上述代码中,调用 t.join()
表示 main 线程会等待 t 线程执行完成或超时,再执 行 main 线程。
- 如果调用空参的 join() 方法,当前线程会一直被阻塞,直到被调用线程执行完成。
- 如果调用有参的 join(long millis) 方法,当前线程会被阻塞指定的时间。如果指定时间内被调用线程执行完成,则结束阻塞状态,执行main线程;如果超出指定时间被调用线程没有执行完成,则main线程会继续执行,而被调用的线程仍然会继续执行直到完成。
因此,如果调用空参的 join() 方法,上述代码会先打印thread end,再打印 main end。如果调用有参的 join(long millis) 方法,由于main线程只等待1秒,而t线程会睡眠2秒,等待时间结束后t线程仍在睡眠,所以会先打印main end,再打印thread end。
2. currentThread() 获取当前线程引用
这个方法在前面就使用过了。
currentThread()
是 Thread
类的静态方法,用于获取当前正在执行的线程对象。当调用 Thread.currentThread()
方法时,会返回表示当前线程的 Thread
对象。
使用场景:
- 在使用匿名内部类或者Lambda表达式中,由于还没有创建线程对象,我们就可以用currentThread()方法获取当前线程引用。
- 创建线程的第二种方式中,由于
Runnable
接口本身并不包含与线程相关的方法,而只有一个run()
方法。因此,在实现Runnable
接口时,需要将其实现类传递给Thread
对象的构造函数,然后通过Thread
对象来创建和管理线程。所以就可以通过Thread.currentThread()
方法来获取当前线程的引用。
而用创建线程的第一种方式中,我们是通过继承Thread类创建线程,就可以直接用this获取当前线程的引用了。
public class Demo7 {
public static void main(String[] args) {
//currentThread() 获取当前线程引用
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
3. sleep() 休眠当前线程
这也是我们前面就一直在使用的方法。
sleep()
方法是 Thread
类的一个静态方法,它让当前线程暂停执行指定的时间。在调用 sleep()
方法后,当前线程会暂时放弃 CPU 的执行权,进入阻塞状态,让其他线程有机会执行。一旦休眠时间结束,线程就会重新进入就绪状态,等待CPU调度执行。
注意事项:
- 在理论上,调用
Thread.sleep()
方法后,线程会暂停执行指定的时间,但实际休眠时间可能会略微超过设置的休眠时间。这是由于操作系统调度和线程切换所带来的额外开销和延迟。 - 具体来说,
Thread.sleep()
方法是通过将当前线程置于休眠状态来实现延迟,但并不保证在精确的时间后唤醒线程。操作系统和 JVM 在处理线程睡眠时可能会引入一些额外的延迟,比如线程调度、上下文切换等。因此,实际休眠时间可能会略微超过设置的休眠时间,但通常不会有太大的偏差。
也就是说,当休眠时间到了之后,线程从阻塞状态恢复到运行状态,但不代表线程就能立即去CPU上执行。
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
long prevTime = System.currentTimeMillis();
Thread.sleep(1000);
long currTime = System.currentTimeMillis();
System.out.println("实际休眠时间:" + (currTime - prevTime));
}
}
在这段代码中,首先记录了调用 Thread.sleep(1000)
前的时间戳 prevTime
,然后让当前线程休眠1秒钟(1000毫秒),接着再获取休眠后的时间戳 currTime
,最后计算并输出两个时间戳之间的差值,即实际休眠时间。
二、线程的状态
1. 线程的所有状态
而 RUNNABLE 状态又可以分成两种状态:1.正在工作中(RUNNING),2.即将开始工作(READY)。
2. 状态转移
- 新建 -> 就绪:当调用线程的
start()
方法启动线程时,线程从新建状态转移到就绪状态。 - 就绪 -> 运行:当线程获取到 CPU 时间片开始执行时,线程从就绪状态转移到运行状态。
- 运行 -> 阻塞:当线程需要等待某个条件满足时,如调用
sleep()
方法或者等待 I/O 操作完成时,线程从运行状态转移到阻塞状态。 - 运行 -> 终止:线程执行完任务或者出现异常导致线程结束时,线程从运行状态转移到终止状态。
- 阻塞 -> 就绪:当线程等待的条件满足或者资源释放后,线程从阻塞状态转移到就绪状态。
- 就绪 -> 终止:线程执行完任务或者出现异常导致线程结束时,线程从就绪状态转移到终止状态。
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
//NEW: 安排了⼯作, 还未开始⾏动
System.out.println(t.getState());
t.start();
//RUNNABLE: 可⼯作的. ⼜可以分成正在⼯作中(RUNNING)和即将开始⼯作(READY).
System.out.println(t.getState());
t.join(); //让main线程等待t线程执行完毕
//TERMINATED: ⼯作完成了.
System.out.println(t.getState());
}
}
1. 已经创建了线程对象,但还未开启线程,处于NEW状态。
2. 开启线程,线程处于执行中,是RUNNABLE状态。
3. t线程执行完成,为TERMINATED状态。
最后看下另外的三种状态(需要使用 jconsole 工具和后续内容锁):
- 阻塞(Blocked):线程被挂起,等待某个条件满足或者等待其他资源释放。比如调用
sleep()
、wait()
方法或者等待 I/O 操作完成时会进入阻塞状态。 - 等待(Waiting):线程进入等待状态,等待其他线程通知唤醒。比如调用
Object.wait()
、Thread.join()
或者LockSupport.park()
方法时会进入等待状态。 - 超时等待(Timed Waiting):与等待状态类似,不同之处在于可以设置一个超时时间,在超时时间到达前等待或者在接收到通知前等待。比如调用
Thread.sleep()
、Object.wait(timeout)
或者Thread.join(timeout)
方法。