线程相关基本方法有wait、notify、notifyAll、sleep、yield、join等。这些方法控制线程之运行,并影响线程状态变化。
线程等待:wait方法
调用wait方法的线程会进入WAITING状态;只有等到其他线程之通知或被中断后才会返回。需注意,在调用wait方法后会释放对象的锁,因此,wait方法一般被用于同步方法 或 同步代码块之中。
线程睡眠:sleep方法
调用sleep会导致当前线程休眠。与wait方法不同之处,sleep方法不会释放当前占有的锁,会导致线程进入TIMED-WAITING状态;而wait方法会导致当前线程进入WAITING状态。
线程让步:yield方法
调用yield方法会使当前线程让出(释放)CPU执行时间片,与其他线程一起重新竞争CPU时间片。
在一般情况下,优先级高的线程更有可能竞争到CPU时间片;但这不绝对,有的OS对线程优先级并不敏感。
线程中断:interrup方法
interrup方法用于向线程发行一个终止通知信号,会影响该线程内部的一个中断标识位,这个线程本身并不会因为调用了interrup方法而改变状态。状态的具体变化需要等待接收到的中断标识的程序的最终处理结果来判定。对interrup方法的理解需注意收下4核心点:
- 调用interrup方法并不会中断一正在运行的线程;即处于Running状态的线程并不会因被中断而终止,仅仅改变了内部维护的中断标识位,而已。核心JDK源码:
/** * Tests whether the current thread has been interrupted. The * <i>interrupted status</i> of the thread is cleared by this method. In * other words, if this method were to be called twice in succession, the * second call would return false (unless the current thread were * interrupted again, after the first call had cleared its interrupted * status and before the second call had examined it). * * <p>A thread interruption ignored because a thread was not alive * at the time of the interrupt will be reflected by this method * returning false. * * @return <code>true</code> if the current thread has been interrupted; * <code>false</code> otherwise. * @see #isInterrupted() * @revised 6.0 */ public static boolean interrupted() { return currentThread().isInterrupted(true); } /** * Tests whether this thread has been interrupted. The <i>interrupted * status</i> of the thread is unaffected by this method. * * <p>A thread interruption ignored because a thread was not alive * at the time of the interrupt will be reflected by this method * returning false. * * @return <code>true</code> if this thread has been interrupted; * <code>false</code> otherwise. * @see #interrupted() * @revised 6.0 */ public boolean isInterrupted() { return isInterrupted(false); }
- 若因调用sleep方法而使线程处于TIMED-WAITING状态,则这时调用interrupt方法会抛出InterruptedException,使线程提前结束TIMED-WAITING状态。
- 许多声明抛出InterruptedException的方法,如Thread.sleep(long mills),在抛出异常前都会清除中断标识位,所以在抛出异常后调用interrupted方法将会返回false。
- 中断状态是线程固有的一个标识位,可通过此标识位安全终止线程。例如,在想终止一个线程时,可以先调用該线程的interrupt方法,然后在线程的run方法中根据該线程 isInterrupted 方法的返回状态值安全终止线程。
public class SafeInterruptThread extends Thread { @Override public void run(){ if(!Thread.currentThread().isInterrupted()){ try { //☆此处处理正常的线程业务逻辑 //sleep会抛出InterruptedException sleep(11); } catch (InterruptedException e) { Thread.currentThread().interrupt(); //重置中断标识 // e.printStackTrace(); } } if(Thread.currentThread().isInterrupted()){ //☆处理线程结束前必要的一些资源释放和清理工作。比如释放锁。 //存储数据到持久化层、发出异常通知等,用于実現线程的安全退出。 try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }
//☆ 定义一个可安分退出的线程 SafeInterruptThread thread = new SafeInterruptThread(); //☆ 安全退出线程 thread.interrupt();
线程加入:join方法
join方法用于等待其它线程终止;如果当前线程中调用另一个线程的join方法,则当前线程转为阻塞状态,等到另一个线程结束,当前线程再由阻塞状态转为就绪状态,等待获取CPU的使用权。在很多情况下,主线程生成并启动了子线程,需要等到子线程返回结果并收集和处理再退出,这时就要用到join方法。具体使用方法如下:
ChildrenThread childrenThread = new ChildrenThread(); //子线程开始运行
childrenThread.join(); //等待子线程childrenThread执行结束
//子线程join()结束,开始运行主线程
线程唤醒:notify方法
Object类中有notify方法,用于唤醒在此对象监视器上等待的一个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意的。
我们通常调用其中一个对象的wait方法在对象的监视器上等待,直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在該对象上主动同步的其他线程竞争。
类似的方法还有notifyAll,用于唤醒在监视器上等待的所有线程。
后台守护线程:setDaemon
setDaemon方法用于定义一个守护线程,也叫作“服务线程”,該线程是后台线程,其有一个特性,即为用户线程提供公共服务,在没有用户线程可服务时会自动离开。
守护线程优先级较低,用于为系统中的其他对象和线程提供服务。将一个用户线程设置为守护线程的方法是在线程对象创建之前用线程对象的setDaemon(true)来设置。
在后台守护线程中定义的线程也是后台守护线程。后台守护线程是JVM级别的,比如GC线程就是一个典型的守护线程,在我们的程序中不再有任何线程运行时,程序就不会再产生垃圾,垃圾回收器也就无事可作,所以在回收JVM上仅剩的线程时,GC线程会自动离开。它始终在低级别的状态下运行,用于实时监控和管理系统中的可回收资源。
守护线程是运行在后台中的一种特殊线程,独立于控制终端并周期性地执行某种任务或等待处理某些已发生事件。也就是说守护线程不依赖于终端,但依赖于JVM,与JVM同生共死。在JVM中的所有线程都是守护线程时,JVM就可以退出了。如果还有非守护线程,则JVM不会退出。
各方法对线程状态的影响
sleep方法与wait方法之区别
sleep方法隶属于Thread类;而wait方法则属于Object类
sleep方法暂停执行指定的时间,让出CPU给其它线程,但其监控状态依然保持,在指定的时间过后又会自动恢复运行状态。
在调用sleep方法之过程中,线程不会释放对象锁。
在调用wait方法时,线程会放弃对象锁,进入等待此对象的等待锁池,只有针对此对象调用notify方法后,該线程才能进入对象锁池准备获取对象锁,并进入运行状态。
start & run
- start 方法用于启动线程;真正実現了多线程运行。在调用了start后,线程会在后台执行,无须等待run方法体的代码执行完毕,就可继续执行下面的代码。
- 调用start启动一线程时,此线程处于就绪状态,并没有运行。
- run方法也叫线程体。调用run后,线程进入运行状态。run结束后线程终止。
终止线程之四种方式
1. 正常运行结束
线程体执行完成。
2. 使用退出标志退出线程
一般情况下,run方法执行完时,线程会正常结束。然而,有些线程是后台线程,需要长时间运行,只有在系统满足某些特殊条件后,才能触发关闭。这时可以使用一个变量来控制,并通过设置这个标志为true或false来控制之。
例:
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
@Override
public void run(){
while (!exit){
//业务逻辑代码
}
}
}
以上代码在线程中定义了一退出标志exit,它的默认值为false。在定义exit时使用了一个Java关键字volatile,这个关键字用于使exit线程同步安全,也就是说在同一时刻仅能有一个线程修改exit的值。在exit为true时,while循环退出。
3. 使用interrupt方法终止线程
使用interrupt方法终止线程有以下2种情况:
1) 线程处于阻塞状态。
例如,在使用了sleep、调用锁的wait或者调用socket的receiver、accept等方法时,会使线程处于阻塞状态。在调用interrupt方法时,会抛出InterruptedException异常。我们通过代码捕获该异常,然后通过break跳出状态检测循环,可以有机会结束这个线程的执行。注意并不是只要调用interrupt方法即可结束线程,一定要先捕获InterruptedException异常再break跳出循环,才能正常结束run方法。
具体实现:
public class ThreadSafe extends Thread {
@Override
public void run() {
while (!isInterrupted()) { //在非阻塞过程中通过判断中断标志来退出
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
break; //在捕获到异常后执行break跳出循环
}
}
}
}
2) 线程未处于阻塞状态
此时 使用isInterrupted方法判断线程的中断标志来退出循环。在调用interrupt方法时,中断标志会被设置为true,并不能立刻退出线程,而是执行线程终止前的资源释放操作,等待资源释放完毕后退出該线程。
4. 使用stop方法终止线程:不安全
在程序中可以直接调用Thread.stop方法强制终止线程,但这是很危险的,可能会产生不可预料的后果。
这就像突然关机一样。
在程序使用Thread.stop方法终止线程时,該线程的子线程会抛出ThreadDeathError错误,并释放子线程持有的所有锁。加锁的代码块一般被用于保护数据的一致性,如果在调用Thread.stop方法后导致该线程所持有的所有锁突然释放而使得锁资源不可控制,被保护的数据就可能出现不一致的情况。