《Java 线程编程》学习笔记5
第5章 完美终止线程
5.1 中断线程:interrupt()
- 当一个线程运行时,另一个线程可以调用对应的 Thread 对象的 interrupt() 方法来中断它:
public void interrupt()
- 这个方法只是在目标线程中设置一个标志位,表示它已经被中断,并立即返回。该方法可能抛出 SecurityException,表示发出中断请求的线程没有权限中断其他线程。
- 在 Thread 上调用
checkAccess()
方法可以进行安全性检查,这个方法又会调用 SecurityManager 的checkAccess(Thread)
方法。
- 在 Thread 上调用
警告:
SecurityException 属于 RuntimeException 的一个子类,因此,对于可能抛出此异常的 Thread 或 ThreadGroup 的任何方法,并不需要 try/catch 块。默认时,应用程序没有定义 SecurityManager。所以,在代码中需要进行一般性检查,使用方法System.getSecurityManager()
。如果返回 null,则表示没有安装 SecurityManager。如果没有返回 null,则调用 SecurityManager 时必须小心。常见覆写 SecurityManager 方法:
import java.io.*;
class PasswordSecurityManager extends SecurityManager {
private String password;
PasswordSecurityManager(String password) {
super();
this.password = password;
}
private boolean accessOK() {
int c;
//DataInputStream dis = new DataInputStream(System.in);
BufferedReader dis = new BufferedReader(new InputStreamReader(System.in));
String response;
System.out.println("What's the secret password?");
try {
response = dis.readLine();
if (response.equals(password))
return true;
else
return false;
} catch (IOException e) {
return false;
}
}
public void checkRead(FileDescriptor filedescriptor) {
if (!accessOK())
throw new SecurityException("Not a Chance!");
}
public void checkRead(String filename) {
if (!accessOK())
throw new SecurityException("No Way!");
}
public void checkRead(String filename, Object executionContext) {
if (!accessOK())
throw new SecurityException("Forget It!");
}
public void checkWrite(FileDescriptor filedescriptor) {
if (!accessOK())
throw new SecurityException("Not!");
}
public void checkWrite(String filename) {
if (!accessOK())
throw new SecurityException("Not Even!");
}
}
5.1.1 中断休眠线程
- 代码示例:
public class SleepInterrupt extends Object implements Runnable {
public void run() {
try {
System.out.println("in run - about to sleep for 20 seconds");
Thread.sleep(20000);
System.out.println("in run() - woke up");
} catch(InterruptionException x) {
System.out.println("in run() - interrupted while sleeping");
return;
}
System.out.println("in run() - doing stuff after nap");
System.out.println("in run() - leaving normally");
}
public static void main(String[] args) {
SleepInterrupt si = new SleepInterrupt();
Thread t = new Thread(si);
t.start();
// 确保新线程有机会运行一段时间
try {
Thread.sleep(2000);
}
catch(InterruptedExcepted x) {
}
System.out.println("in main() - interrupting other thread");
t.interrupt();
System.out.println("in main() - leaving");
}
}
/*
运行结果:
in run() - about to sleep for 20 seconds
in main() - interrupting other thread
in main() - leaving
in run() - interrupting while sleeping
*/
5.1.2 待决中断
- 上一个示例说明在线程 sleep() 时,会被 interrupt()。另外,如果在调用 sleep() 之前,中断已经被调用,那它会立即抛出 InterruptedException。
public class PendingInterrupt extends Object {
public static void main(String[] args) {
if(args.length > 0) {
Thread.currentThread().interrupt();
}
}
long startTime = System.currentTimeMillis();
try {
Thread.sleep(2000);
System.out.println("was Not interrupted.");
}
catch (InterruptedException x) {
System.out.println("was interrupted");
}
System.out.println("elapsedTime = " + (System.currentTimeMillis() - startTime));
}
/*
如果不带参数执行:
was Not interrupted
elapsedTime = 2080
带参数执行:
was interrupted
elapsedTime = 110
*/
5.1.3 使用 isInterrupted()
- 可以在 Thread 对象上调用 isInterrupted() 方法来检查任何线程的中断状态。
public boolean isInterrupted()
5.1.4 使用 Thread.interrupted()
- 如果线程被中断,调用该函数,将返回 true,同时清除中断标志位。
public static boolean isInterrupted()
5.1.5 InterruptedException
5.2 挂起和恢复线程运行
- 加入某个程序使用一个线程按顺序翻转图片达到动画显示的目的,当动画不可见时,就没有必要继续动画显示,直到窗口可见后,再次恢复动画。
5.2.1 使用淘汰的方法 suspend() 和 resume()
技巧:
这些方法和类已经被 Sun 公司淘汰,说明开发人员应该尽量避免使用它们。淘汰的方法仍然可以使用,但是编译代码时,就会发出警告。淘汰这些方法或类,表示他们过时了,或者使用时比较危险,有可能在将来的 JDK 版本中删除。
suspend()
方法是在 JDK 1.2 中淘汰的方法,因为如果在不合适的时候挂起线程(如锁定共享资源时),此时可能会发生死锁条件(deadlock condition)。
5.2.2 在不恰当的时候挂起
- 下面的代码示例通过休眠来减缓运行,使线程更可能在不适当的时候被挂起。
5.2.3 不使用淘汰方法实现挂起和恢复
- 示例代码:
public class AlternateSuspendResume extends Object implements Runnable {
private volatile int firstVal;
private volatile int secondVal;
private volatile boolean suspended; // 等待状态变量,用于跟踪让内部线程临时终止运行的请求
public boolean areValesEqual() {
return (firstVal == secondVal);
}
public void run() {
try {
suspend = false;
firstVal = 0;
secondVal = 0;
workMethod();
} catch(InterruptedException x) {
System.out.println("interrupted while in workMethod()");
}
}
private void workMethod() throws InterruptedException {
int val = 1;
while(true) {
// 仅当挂起时才运行的代码
waitWhileSuspended();
stepOne(Val);
stepTwo(Val);
val++;
// 仅当挂起时才运行的代码
waitWhileSuspended();
Thread.sleep(200);
}
}
public void suspendRequest() {
suspended = true;
}
public void resumeRequest() {
suspended = false;
}
private void waitWhileSuspended() throws InterruptedException {
// 这是一个『繁忙等待』技术的示例
// 它是非等待条件改变的最佳途径
// 因为它会不断请求处理器周期来检查执行
// 更佳的技术是:使用 Java 内置的『等待-通知』机制
while(suspended) {
Thread.sleep(200);
}
}
}
技巧:
如果在代码中有多个安全的地方可以挂起线程,对所有这些安全地方,应该添加更多的waitWhileSuspended()
方法调用。只要确保没有在持有锁时允许挂起就行了!应该频繁使用waitWhileSuspended()
,有助于线程对挂起请求的快速反应。同时,要记住,调用waitWhileSuspended()
会耗费一些处理器资源,因此,也不要用得太频繁。
5.3 终止线程
5.3.1 使用淘汰的方法 stop()
- JDK 1.2 淘汰了
stop()
方法,因为它可能导致对象中数据的崩溃。 - 一个问题是,当线程突然终止时,很少有机会会执行清理工作。
- 另一个问题,当在某个线程上调用
stop()
方法时,线程释放它当前持有的所有锁。持有这些锁必定有某种合适的理由 —— 也许是组织其他线程访问尚未处于一致性状态的数据。突然释放锁可能使某些对象中的数据处于不一致的状态,而且不出现数据可能崩溃的任何警告。
5.3.2 取代 stop()
- 作为一种直接终止线程的替代技术,可使用 boolean 指示变量来判断线程是否应该继续执行。
public class AlternateStop extends Object implements Runnable {
private volatile boolean stopRequested;
private Thread runThread;
public void run() {
runThread = Thread.currentThread();
stopRequested = false;
int count = 0;
while(! stopRequested) {
System.out.println("Running ... count = " + count);
count++;
try {
Thread.sleep(300);
} catch(InterruptedException x) {
Thread.currentThread().interrupt(); // 重新申明
}
}
}
/*
函数说明: 将 stopRequested 设置成 true,并中断线程的执行
*/
public void stopRequest() {
stopRequest = true;
if(runThread != null) {
runThread.interrupt();
}
}
}
5.5 守护线程
- 标记为守护(daemon)的线程将以全新的方式终止。守护线程用于后台支持任务,而且仅在普通,非守护线程仍然运行时才需要。当 VM 检测到只剩下一个守护线程时,就会退出。如果非守护线程仍然存活,VM 就不会退出。守护线程提供了一种管理某些后台处理的理想方法,它们只是在支持其他非守护线程时才需要。
- 示例代码:
public class DaemonThread extends Object implements Runnable {
public void run() {
System.out.println("entering run()");
try {
System.out.println("in run() - currentThread() = " + Thread.currentThread());
while(true) {
try {Thread.sleep(500);}
catch(InterruptedException x) {}
System.out.println("in run() - woke up again")
}
} finally {
System.out.println("in run() - leaving run()")
}
}
}
public class DaemonThreadMain extends Object {
public static void main(String[] args) {
System.out.println("in main() - entering main()");
Thread t = new Thread(new DaemonThread());
t.setDaemon(true); // 这里设置为守护线程
t.start();
try {Thread.sleep(30000);}
catch(InterruptedException x) {}
System.out.println("in main() - leaving main()")
}
}
警告:虽然守护线程非常有用,但是必须确保其他所有非守护线程消亡时,不会由于它的终止而产生危害。