Java 是一门支持多线程的语言,也提供了一些关于线程控制的方法,使开发者在做一些多线程编程的时候能够实现更加丰富的功能,下面列出常见的几种方法以及它对线程的控制情况:
wait():运行-阻塞notify():阻塞-就绪sleep():运行-阻塞join():运行-阻塞interrupt():阻塞-就绪yield():运行-就绪
一般的控制效果如上所示,下面对各方法进行更加详细的介绍。
wait()
基本功能:阻塞当前线程,并释放已经获得的锁。
wait()和notify()是 Object 类提供的关于线程控制的方法,多用于线程同步之类的。在一个线程中要调用某个对象的wait()方法,首先要获得这个对象的锁,使用 synchronized 关键字,通过这个关键字获得对象锁之后,其他所有需要此对象锁的线程都会被阻塞,直到当前拥有锁的线程释放掉锁,释放锁有两种方式:执行完成或者是调用wait()方法进入阻塞。当调用了wait()方法之后,其他所有等待锁的线程将争夺这个对象锁。同一时刻只有一个线程能够获得对象锁,也只有一个线程能够运行。
wait()基本写法:
Object lock = new Object();
synchronized(lock) {
// ...
lock.wait();
// ...
}
notify()
基本功能:唤醒正在等待当前锁的线程。
与wait()方法相对应,当一个线程中调用了某个对象的wait()方法之后,当前线程就进入阻塞状态,等待一段时间之后自动唤醒(如果等待时间为 0 ,那这个线程就无法自动唤醒),当一个线程在等待的过程中,其他的线程则可以通过调用对象的notify()方法,随机唤醒一个正在等待的线程,使之能够继续往下运行。但是当有多个线程在等待的时候,notify()方法只能唤醒一个线程,这是由系统决定的,但是有可能会出现notify()没有唤醒它真正想要唤醒的那一个线程,此时就可以使用notifyAll()方法,此方法可以唤醒所有的等待线程,但是这又会出现另一个问题:唤醒了本不该被唤醒的线程。所以为了解决这种问题,在调用wait()的地方一般都会设置一个循环,当这个线程被唤醒之后,首先判断自己所需的条件是否满足,如果不满足那还要继续等待。
与notifyAll()搭配的wait()基本写法:
Object lock = new Object();
boolean isOk;
synchronized(lock) {
// ...
while(!isOk) {
lock.wait();
}
// ...
}
sleep()
基本功能:阻塞当前线程一段时间,不释放资源。
这个方法与wait()方法有些类似,都可以阻塞当前线程,但是与wait()方法不同的是sleep()方法被调用后,虽然当前线程被阻塞,但并不会释放诸如对象锁等资源,也无法被notify()方法唤醒,不过与wait()方法不同的是,sleep()方法并没有永久休眠这一说,当其参数为 0 的时候,会由运行态转变为就绪态,然后多个线程之间再争夺 CPU 资源,这一点倒是与yield()类似。还有一点,由于调用sleep()之后,线程会首先休眠一段指定的时间,然后进入就绪态等待 CPU 资源,所以也就意味着,在指定的一段时间之后线程可能并不会立刻执行。
join()
基本功能:等待,直到当前线程死亡或者达到等待的最大时间(如果参数为 0 ,则永远等待)。
源码:
public final void join(long millis)
throws InterruptedException {
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
这个方法的实现使用到了wait(),根据wait()的用法也知道,当一个线程调用了某个线程的join()方法之后,这个线程就会进入阻塞状态,直到这个线程死亡或超时。join 这个方法名也很形象:将另一个线程加入到当前位置执行,并阻止当前线程的运行,直到这个线程执行完毕。
至于这个方法的用处,比如有两个任务,其中任务 A 执行的过程中需要使用到任务 B 执行的结果,那么一般情况下可以这么做:将 A B 放到一个线程中执行,先执行 B ,再执行 A ,那么为了更加高效的执行这两个任务,是否可以利用多线程完成这两个任务?首先,构造两个线程分别运行 A B 任务,然后问题来了,在 A 任务执行过程中,需要用到了 B 的结果,A 不知道 B 到底有没有完成,也不知道 B 什么时候完成,又该怎么办?此时就可以调用任务 B 所在线程的join()方法,使得 A 所在线程停止工作,一直等待任务 B 工作完成。或者 A 如果要每隔一段时间汇报一下任务的进展情况,那便不能死等 B 结束,此时可以调用join(long)方法,等待一段时间之后再回来汇报一下进展,然后继续调用join(long)等待一段时间。这便能更加充分地发挥多线程的优势,在 A 任务的前半部分工作中,不需要知道任务 B 的结果,那么两个任务并行运行,当 A 需要等待 B 的结果是,A 所在的线程就会阻塞,所以此时事实上变成了单线程工作:只有 B 所在的线程在工作,B 结束之后,还是一个事实上的单线程工作:只有 A 所在的线程在工作。
interrupt()
基本功能:将线程的中断位标志为 true 。
关于线程中断的方法有三个:interrupt(): 标志中断位为 ture,isInterrupted() interrupted(): 返回线程中断位的值。以及还有一个相关的异常 InterruptedException 。
调用interrupt()这个方法本身只能标志线程的中断位,这导致的结果与当前线程的状态(运行或阻塞)有关,当线程处于运行状态时,调用interrupt()对线程来说几乎没有影响,只会将中断标志位置为 ture , 用户可以通过主动调用isInterrupted()或interrupted()才能得到当前的中断标志位。但是当线程处于阻塞状态时,如果在另一个线程中调用了这个线程的interrupt()方法,就会导致其抛出 InterruptedException ,但不会再标志中断位,所以这也就是为什么所有线程的阻塞方法wait() join() sleep()在使用的时候一定要捕获 InterruptedException 的原因,也就是说,当一个被阻塞的过程中被中断的话,就会强制被唤醒,处理 InterruptedException 异常。
那么这个方法在编程中能起到什么作用?这就要说到线程的启动与停止,线程的启动对应着start()方法,同样线程的停止应该是对应着stop()方法,但是在 Thread 类中stop()是一个被弃用的方法,而interrupt()正是用来替代stop()的方法。一个子线程多用来执行一些耗时操作,那么如果需要在主线程中停止一个子线程的时候,是否可以直接销毁这个线程?当然不行,子线程中很可能会有一些 IO 操作,盲目的销毁只会造成垃圾文件的累积,所以,当主线程需要停止子线程的时候,更合适的方法是通知子线程准备停止,然后子线程决定自己是否要停止以及何时停止(如需要先清理运行过程中产生的垃圾文件),这就是interrupt()发挥作用的地方:主线程可以调用子线程的这个方法将中断标志位置 true ,子线程通过调用isInterrupted()方法判断是否应该停止,或者通过捕获 InterruptedException 开始执行停止的准备工作。这样一来就给了子线程足够的自由度处理自己的后事,但是同样也给了子线程过多的自由,子线程完全可以忽略这个通知继续埋头干自己的事。
再说说isInterrupted()和interrupted(),它们都是返回当前的中断标志位状态,但是前者只包含读的操作,后者不仅会读取中断位状态,还会对中断标志位进行擦除,也就是说在调用了interrupted()方法之后,线程的中断标志位就会被置 false 。
yield()
基本功能:提示调度器,让出 CPU 使用权。
这是一个 native 方法,在当前线程运行的过程中调用此方法,可以使当前线程由运行状态变为就绪状态,但是这并不是一个强制性方法,按照注释的意思,这个方法只是给调度器提出了一个让出 CPU 使用权的建议,真正确定是否让出 CPU 的,还是要看调度器的意思。
这个方法也可以用来协调多个线程之间的工作,对于上面join()所讲的例子,也可以使用这个方法解决。假设有两个任务 A B 分别工作于两个线程,但是 A 任务执行的某个过程需要使用到任务 B 的结果,此时可以使用join()方法阻塞 A 线程让 B 全力工作,但是前提是任务 A 所在线程需要持有 B 所在线程的实例,才能调用它的方法,否则就不能使用join(),对于这种问题yield()给出了解决方案,任务 A 会判断任务 B 是否完成(如规定任务 B 完成之后更改某个值),如果 B 任务还没有完成,那就让出 A 所在线程的 CPU 使用权,让所有线程自由竞争 CPU ,然后当 A 线程再竞争到 CPU 的时候如果 B 还是没有好,那就再让出,最终肯定能够等到 B 任务完成的时候。