基础线程机制
Daemon
Java 中有两类线程:
- 用户线程(User Thread):运行在前台的线程,比如主线程。
- 守护线程(Daemon Thread):程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
一旦所有的用户线程都退出了,虚拟机也就退出了,因此不要在守护线程中执行业务逻辑操作,因为随时都可能中断(甚至无法执行 finnally 中的语句)。
注意:在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
start() 和 run()
- start()
启动一个新线程,新线程会执行 run() 方法;
start 不能被重复调用,如果重复调用,会抛java.lang.IllegalException
。 - run()
和一个成员方法相同,可以被重复调用;
单独调用 run() 不会启动新线程
sleep() 和 wait()
sleep() 和 wait() 的区别:
- 持有者
sleep() 是 Thread 的静态方法;
wait() 是 Object 的成员方法,所以任何对象都可以调用 wait() - 锁是否会释放
sleep() 会一直持有同步锁,继续占用 CPU 资源;
wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法 - 使用范围
wait() 只能在同步代码块或同步方法中使用,否则会抛java.lang.IllegalMonitorStateException
;
sleep() 可在任何地方使用
yield() 和 wait()
Thread.yield() 声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
yield() 和 wait() 的区别:
- yield() 是 Thread 的静态方法
wait() 是 Object 的成员方法 - yield() 让当前线程由“运行状态”进入“就绪状态”;
wait() 让当前线程由“运行状态”进入“等待状态” - yield() 不会释放同步锁;
wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法
notify() 和 notifyAll()
锁池:线程 A 已经拥有某个对象的锁,线程 B、C 想要调用这个对象的同步方法,此时线程 B、C 就会被阻塞,进入一个地方去等待锁的释放,这个地方就是该对象的锁池。
等待池:假设线程 A 调用了某个对象的 wait() 方法,线程 A 释放该对象的锁,同时线程 A 进入等待池,进入等待池的线程不会竞争该对象的锁。
notifyAll() 让所有处于等待池的线程进入锁池去竞争锁。
中断线程
Sleep 来暂停执行
Thread.sleep 可以让当前线程执行暂停一个时间段,这样处理器时间就可以给其他线程使用。
sleep 有两种重载形式:一个是指定睡眠时间为毫秒,另外一个是指定睡眠时间为纳秒级。然而,这些睡眠时间不能保证是精确的,因为它们是通过由操作系统来提供的,并受其限制,因而不能假设 sleep 的睡眠时间是精确的。此外,睡眠周期也可以通过中断终止,我们将在后面的章节中看到。
// SleepMessages 示例使用 sleep 每隔4秒打印一次消息:
public class SleepMessages {
public static void main(String[] args) throws InterruptedException {
String importantInfo[] = { "Mares eat oats", "Does eat oats",
"Little lambs eat ivy","A kid will eat ivy too" };
for (int i = 0; i < importantInfo.length; i++) {
// Pause for 4 seconds
Thread.sleep(4000);
// Print a message
System.out.println(importantInfo[i]);
}
}
}
请注意 main 声明抛出 InterruptedException。当 sleep 是激活的时候,若有另一个线程中断当前线程时,则 sleep 抛出异常。由于该应用程序还没有定义的另一个线程来引起的中断,所以考虑捕捉 InterruptedException。
中断(interrupt)
中断是表明一个线程,它应该停止它正在做和将要做的事。线程通过在 Thread 对象调用 interrupt 来实现线程的中断。为了中断机制能正常工作,被中断的线程必须支持自己的中断。
支持中断
如何实现线程支持自己的中断?这要看是它目前正在做什么。如果线程调用方法频繁抛出 InterruptedException 异常,那么它只要在 run 方法捕获了异常之后返回即可。例如 :
for (int i = 0; i < importantInfo.length; i++) {
// Pause for 4 seconds
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// We've been interrupted: no more messages.
return;
}
// Print a message
System.out.println(importantInfo[i]);
}
很多方法都会抛出 InterruptedException,如 sleep,被设计成在收到中断时立即取消他们当前的操作并返回。
若线程长时间没有调用方法抛出 InterruptedException 的话,那么它必须定期调用 Thread.interrupted ,该方法在接收到中断后将返回 true。
for (int i = 0; i < inputs.length; i++) {
heavyCrunch(inputs[i]);
if (Thread.interrupted()) {
// We've been interrupted: no more crunching.
return;
}
}
在这个简单的例子中,代码简单地测试该中断,如果已接收到中断线程就退出。在更复杂的应用程序,它可能会更有意义抛出一个 InterruptedException:
if (Thread.interrupted()) {
throw new InterruptedException();
}
注:interrupt()方法同样也不是一个正确的退出线程的方法,即便调用了该方法,该方法会抛出中断异常,但线程还是会继续运行。
中断状态标志
中断机制是使用被称为中断状态的内部标志实现的。调用 Thread.interrupt 可以设置该标志。当一个线程通过调用静态方法 Thread.interrupted 来检查中断,中断状态被清除。非静态 isInterrupted 方法,它是用于线程来查询另一个线程的中断状态,而不会改变中断状态标志。
按照惯例,任何方法因抛出一个 InterruptedException 而退出都会清除中断状态。当然,它可能因为另一个线程调用 interrupt 而让那个中断状态立即被重新设置回来。
join 方法
join 方法允许一个线程等待另一个完成。假设 t 是一个正在执行的 Thread 对象,那么
t.join();
它会导致当前线程暂停执行直到 t 线程终止。join 允许程序员指定一个等待周期。与 sleep 一样,等待时间是依赖于操作系统的时间,同时不能假设 join 等待时间是精确的。
像 sleep 一样,join 并通过 InterruptedException 退出来响应中断。
终止线程
1. 退出标志
使用退出标志,使线程正常退出,也就是 run 方法完成后线程终止。
需要 while() 循环以某种特定的条件下退出,最直接的方法就是设计一个 boolean 类型的标志,并且通过设置这个标志来控制循环是否退出。
一般需要加上 volatile 来保证标志的可见性。
2. Thread.stop()
Thread.stop() 强制终止线程(不建议使用)。
3. interrupt() 中断线程
- 线程处于阻塞状态,使用 interrupt() 则会抛出 InteruptedException 异常。
(线程处于阻塞状态) - 使用 while(! isInterrupted()) {…} 来判断线程是否被中断,使用 interrupt() 则线程终止。
(线程处于运行状态)
综合线程处于“阻塞状态”和“运行状态”的终止方式,通用的终止线程的形式如下:
try{
while(!isInteruppted()){ //isInteruppted() 保证只要中断标记为 true,就终止线程
//...
}
}catch(InteruptedException e){
//保证当发生 InteruptedException 时,线程被终止。
}
举例如下:
public class InterruptExample implements Runnable{
@Override
public void run() {
int i = 0;
try {
//isInteruppted() 保证只要中断标记为 true,就终止线程
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(100);
i++;
System.out.println(Thread.currentThread().getName() + " (" +
Thread.currentThread().getState() + ") loop " + i);
}
} catch (InterruptedException e) {
// 保证当发生 InteruptedException 时,线程被终止。
System.out.println(Thread.currentThread().getName() + " (" +
Thread.currentThread().getState() + ") catch InterruptedException.");
}
}
public static void main(String[] args) throws InterruptedException {
Runnable interruptTask = new InterruptExample();
Thread t1 = new Thread(interruptTask, "t1");
System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
t1.start(); // 启动“线程t1”
System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
// 主线程休眠300ms,然后主线程给t1发“中断”指令。
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
// 主线程休眠300ms,然后查看t1的状态。
Thread.sleep(300);
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
}
}
/* 结果如下:
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.
*/