java并发编程实践基础(五)
----------
线程的基本控制
线程创建后,可以执行start()方法启动线程,根据线程任务的特性和线程之间的协调性要求,需要对线程进行控制。对线程的控制通常是通过调用Thread对象的方法实现的,主要有sleep(),suspend(),resume(),join(),interrupt()和stop()。一般情况下方法的调用会引起线程状态的转变。
1.
使用sleep暂停执行
Thread.sleep()使用当前线程的执行暂停一段指定的时间,这可以有效的使应用程序的其他线程或者运行在计算机上的其他进程可以使用处理器时间。该方法不会放弃除CPU之外的其它资源。sleep有两个重载的版本,一个以毫秒指定睡眠时间,另一个以纳秒指定睡眠时间,但并不保证这些睡眠时间的精确性,因为他们受到系统计时器和调度程序精度和准确性的影响。另外中断(interrupt)可以终止睡眠时间,在任何情况下,都不能假设调用sleep就会按照指定的时间精确的挂起线程。
package control;
public class SleepTest{
public static void main(String[] arg){
String[] args = {"one","two","three","for"};
long start = System.nanoTime();
for(int i=0;i<args.length;i++){
try{
System.out.println(args[i]);
//休眠主线程
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
long end=System.nanoTime();
System.out.println("总的时间: " + (end-start)/1000000);
}
}
需要注意的是,sleep()方法声明可以抛出InterruptedException异常,当另一个线程中断了已经启动sleep的当前线程时会抛出这个异常。上面的程序只有主线程,不需要考虑这个问题。
2.
使用join等待另外一个线程结束
join方法让一个线程等待另一个线程的完成,如果t1,t2是两个Thread对象,在t1中调用t2.join(),会导致t1线程暂停执行,直到t2的线程终止。join的重载版本允许程序员指定等待的时间,但是和sleep一样,这个时间是不精确的。
package control;
public class JoinTest extends Thread{
static int result = 0;
public JoinTest(String name){
super(name);
}
public static void main(String[] args){
System.out.println("主线程执行");
Thread t = new JoinTest("计算线程");
t.start();
System.out.println("result:"+result);
try{
long start = System.nanoTime();
t.join();
long end = System.nanoTime();
System.out.println((end-start)/1000000+"毫秒后:"+result);
}catch(InterruptedException e){
e.printStackTrace();
}
}
@Override
public void run(){
System.out.println(this.getName()+"开始计算...");
try{
Thread.sleep(4000);
}catch(InterruptedException e){
e.printStackTrace();
}
result = (int)(Math.random()*10000);
System.out.println(this.getName()+"结束计算:");
}
}
执行结果如下:
主线程执行
result:0
计算线程开始计算...
计算线程结束计算:
4000毫秒后:6155
可以把t.join()修改为t.join(2000)。观察输出结果,发现主线程并没有等待计算线程执行结束,就输出结果了。
主线程执行
result:0
计算线程开始计算...
1999毫秒后:0
计算线程结束计算:
3.
使用中断(Interrupt)取消线程
已经启动的线程是活跃的,即
isAlive()方法返回true,线程终止之前一直是活跃的。有
三种方法可以使线程终止:a.run()方法正常返回;b.run()方法意外结束;c.应用程序终止。
经常会碰到这样的情况,我们创建了执行某项工作的线程,然后在他完成之前需要取消这项工作。要使线程在完成任务之前可取消,必须采取一定的措施,但应该是一个清晰而安全的机制使用线程终止。我们可以
通过中断(Thread.interrupt)线程来请求取消,并且让线程来监视并响应中断。
中断请求通常是用户希望能够终止线程的执行,但并不会强制终止线程,但是它会中断线程的睡眠状态,比如调用sleep和wait方法后。
线程自己检查中断状态并终止线程比直接调用stop()方法要安全很多,因为线程可以保存自己的状态。
并且stop()方法已经不推荐使用了。和
中断线程有关的方法有:a.interrupt,向线程发送中断;b.isInterrupted,测试线程是否已经被中断;c.interrupted,测试当前线程是否已经被中断,随后清除线程"中断"状态的静态方法。线程的中断状态只能由线程自己清除,当线程侦测到自己被中断时,经常需要在响应中断之前做某些清除工作,这些清除工作可能涉及那些在线程仍然保持中断状态时会受到影响的操作。如果被中断的线程正在执行sleep,或者wait方法,就会抛出InterruptedException异常。这种抛出异常的中断会清除纯程的中断状态。大体上任何执行阻塞操作的方法,都应该通过Interrupt来取消阻塞操作。
下面的程序,主线程在等待计算线程2000毫秒后,中断计算线程,计算线程由于正在执行sleep,就会抛出InterruptedException异常,终止休眠状态,然后进入异常处理,在catch中可以做一些清理工作(如果需要),然后线程执行结束。
package control;
public class InterruptTest extends Thread{
static int result = 0;
public InterruptTest(String name){
super(name);
}
public static void main(String[] args){
System.out.println("主线程执行");
Thread t = new InterruptTest("计算线程");
t.start();
System.out.println("result: "+result);
try{
long start = System.nanoTime();
t.join(2000);
long end = System.nanoTime();
t.interrupt();
System.out.println((end-start)/1000000+"毫秒后:"+result);
}catch(InterruptedWxception e){
e.printStackTrace();
}
}
@Override
public void run(){
System.out.println(this.getName()+"开始计算...");
try{
Thread.sleep(4000);
}catch(InterruptedWxception e){
System.out.println(this.getName()+"被中断,结束");
return;
}
result = (int)(Math.random()*10000);
System.out.println(this.getName()+"结束计算");
}
}
下面是输出结果:
主线程执行
result:0
计算线程开始计算...
1999毫秒后:0
计算线程被中断,结束
从输出结果中可以看出,计算线程被中断后,run()方法中的最后两行语句没有执行。没有产生计算结果。
如果一个线程长时间没有调用能够抛出InterruptedException异常的方法,那么线程就必须定期的调用Thread.interrupted方法,如果接收到中断就返回true,然后就可以退出线程。
package control;
public class InterruptTest2 extends Thread{
static int result = 0;
public InterruptTest2(String name){
super(name);
}
public static void main(String[] args){
System.out.println("主线程执行");
Thread t = new InterruptTest2("计算线程");
t.start();
System.out.println("result: "+result);
try{
long start = System.nanoTime();
t.join(10);
long end = System.nanoTime();
t.interrupt();
System.out.println((end-start)/1000000+"毫秒后:"+result);
}catch(InterruptedException e){
e.printStackTrace();
}
}
@Override
public void run(){
System.out.println(this.getName()+"开始计算...");
for(int i=0;i<100000;i++){
result++;
if(Thread.interrupted()){
System.out.println(this.getName()+"被中断");
return;
}
}
System.out.println(this.getName()+"结束计算");
}
}
输出结果如下:
主线程执行
result:0
计算线程开始计算...
计算线程被中断
10毫秒后:18555
上面的程序,计算线程原计划执行100000次循环,主线程等待10毫秒后,中断计算线程,计算线程接收到中断后,就可以结束执行了。在更加复杂的应用程序中,当线程收到中断信号后,抛出InterruptedException异常可能更有意义。把中断处理代码集中在cach子句中。
if(Thread.interrupted()){
System.out.println(this.getName()+"被中断");
throw new InterruptedException();
}