Java并发编程实践之线程的基本控制

阅读更多

     线程创建后,可以执行start()方法启动线程,根据线程任务的特性和线程之间的协调性要求,需要对线程进行控制。对线程的控制通常是通过调用Thread对象的方法实现的,主要有sleep()、suspend()、resume()、join()、interrupt()和stop方法。一般情况下方法的调用会引起线程状态的转变。

1、使用Sleep暂停执行

     Thread.sleep()使当前线程的执行暂停一段指定的时间,这可以有效的使应用程序的其他线程或者运行在计算机上的其他进程可以使用处理器时间。该方法不会放弃CPU之外的其他资源。Sleep有两个重载的版本,一个以毫秒指定睡眠时间,另一个以纳秒指定睡眠时间,但并不保证这些睡眠时间的精确性,因为它们收到系统计时器和调度程序精度和准确性的影响。另外中断(interrupt)可以终止睡眠时间,在任何情况下,都不能假设调用sleep就会按照知道指定

的时间准确的挂起线程。

public class TestSleep {
	public static void main(String[] arg) {
		String[] args={"one","two","three","four"};
		long start=System.nanoTime();
		for(int i=0;i 
 

     需要注意的是,sleep()方法声明可能会抛出InterruptedException异常,当另一个线程中断了已经启动sleep的当前线程时机会抛出这个异常。上面的程序只有主线程,不需要考虑这个问题。

2、使用join等待另外一个线程结束

     join方法让一个线程等待另一个线程的完成,如果t1、t2是两个Thread对象,在t1中调用t2.join(),会导致t1线程暂停执行,直到t2的线程终止。join的重载版本运行程序员指定等待的时间,当时和sleep一样,这个时间是不精确地。

public class TestJoin extends Thread{
	static int result=0;
	public TestJoin(String name){
		super(name);
	}
	public static void main(String[] args) {
		System.out.println("主线程执行");
		Thread t=new TestJoin("计算线程");
		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 (Exception e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		System.out.println(this.getName()+"开始计算:...");
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		result=(int) (Math.random()*10000);
		System.out.println(this.getName()+"计算结束:...");
	}
}

执行结果如下:

主线程执行
result:0
计算线程开始计算:...
计算线程计算结束:...
3993毫秒后:4462

上面的程序中,计算线程在计算的时候休眠了4000毫秒,在主线程中调用了t.join()后,主线程等待计算线程执行结束,然后输出结果。

可以把t.join()修改为t.join(2000)。

public class TestJoin extends Thread{
	static int result=0;
	public TestJoin(String name){
		super(name);
	}
	public static void main(String[] args) {
		System.out.println("主线程执行");
		Thread t=new TestJoin("计算线程");
		t.start();
		System.out.println("result:"+result);
		try {
			long start=System.nanoTime();
			t.join(2000);
			long end=System.nanoTime();
			System.out.println((end-start)/1000000+"毫秒后:"+result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		System.out.println(this.getName()+"开始计算:...");
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		result=(int) (Math.random()*10000);
		System.out.println(this.getName()+"计算结束:...");
	}
}

观察输出结果,发现主线程并没有等待计算线程执行结束,就输出结果了。

主线程执行
result:0
计算线程开始计算:...
1990毫秒后:0
计算线程计算结束:...

3、使用中断(Interrupt)取消线程

已经启动的线程是活跃的,即isAlive()方法返回trur,线程终止之前一直是活跃的。有三种方法可以使线程终止:

1)run()方法正常返回;

2)run()方法以外结束;

3)应用程序终止。

      经常会碰到这样的情况,我们创建了执行某项工作的线程,然后再它完成之前需要取消这样工作。要使线程在完成任务之前可取消,必须采取一定的措施,但应该是一个清晰而安全的机制使线程终止。我们可以通过中断(Thread.interrupt)线程来请求取消,并且让线程来监视并响应中断。中断请求通常是用户希望能够终止线程的执行,但并不会强制终止线程,但是它会中断线程的睡眠状态,比如调用sleep和wait方法后。线程自己检查中断状态并终止线程比直接调用stop方法要安全很多,因为线程可以保存自己的状态。并且stop()方法已经不推荐使用了。

和中断线程有关的方法有:

1)interrupt,向线程发送中断;

2)isInterrupted,测试线程是否已经被中断;

3)Interrupt,测试线程是否已经被中断,随后清除线程的“中断”状态。

      线程的中断状态只有线程自己清除,当线程侦测到自己被中断时,经常需要在响应中断之前做某些清除工作,这些清除工作可能涉及那些在线程仍然保持中断状态时会受到影响的操作。如果被中断的线程正在执行sleep,或者wait方法,就会抛出InterruptException异常。这种抛出异常的中断会清除线程的中断状态。大体上任何执行阻塞操作的方法,都应该通过Interrupt来取消阻塞操作。

     下面的程序,主线程在等待计算线程2000毫秒后,中断计算线程,计算线程由于正在执行sleep,就会抛出InterruptException异常,终止休眠状态,然后进入异常处理,在catch中可以做一些清理工作(如果需要),然后线程执行结束。

这是一种典型的终止线程执行的方法:

public class TestInterrupt extends Thread {

	static int result=0;
	public TestInterrupt(String name){
		super(name);
	}
	public static void main(String[] args) {
		System.out.println("主线程执行");
		Thread t=new TestInterrupt("计算线程");
		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 (Exception e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		System.out.println(this.getName()+"开始计算:...");
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			System.out.println(this.getName()+"被中断,结束");
			return;
		}
		result=(int) (Math.random()*10000);
		System.out.println(this.getName()+"计算结束:...");
	}
}

 输出结果如下:

主线程执行
result:0
计算线程开始计算:...
1991毫秒后:0
计算线程被中断,结束

    从输出结果中可以看出,计算线程被中断后,run()方法中的最后两行语句没有执行。没有产生计算结果。

t.interrupt()不会中断正在执行的线程,只是将线程的标志位设置成true。但是如果线程在调用sleep(),join(),wait()方法时线程被中断,则这些方法会抛出InterruptedException,在catch块中捕获到这个异常时,线程的中断标志位已经被设置成false了,因此在此catch块中调用t.isInterrupted(),Thread.interrupted()始终都为false。

如果一个线程长时间没有调用能够抛出InterruptException异常的方法,那么线程就必须定期的调用Thread.interrupted方法,如果接收到中断就返回true,然后就可以退出线程。

public class TestInterrupt extends Thread {

	static int result=0;
	public TestInterrupt(String name){
		super(name);
	}
	public static void main(String[] args) {
		System.out.println("主线程执行");
		Thread t=new TestInterrupt("计算线程");
		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 (Exception 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
计算线程开始计算...
计算线程被中断
7毫秒后:18563
false

 上面的程序,计算线程原计划执行100000次循环,主线程等待10毫秒后,中断计算线程,计算线程接收到中断后,就可以结束执行了。在更加复杂的应用程序中,当线程收到中断信号后,抛出InterruptException异常更有意义。把中断处理代码集中到catch字句中。

4、使用stop终止线程

      在Thread类中提供了Stop方法,来强迫线程停止执行。但是现在已经过时了。

      该方法具有固定的不安全性。用Thread.Stop来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查     ThreadDeath异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。Stop的许多使用方式都应由只修改某些变量以知识目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用interrupt方法来中断该等待。

     无论该线程在做些什么,它所代表的线程都被迫异常停止,并抛出一个新创建的ThreadDeath对象作为异常。停止一个尚未启动的线程是允许的。如果最后启动了该线程,它会立即终止。

     应用程序通常不应视图捕获ThreadDeath,除非它必须执行某些异常的清楚操作(注意,抛出ThreadDeath将导致try语句的finally字句在线程终止前执行)。如果catch字句捕获了一个ThreadDeath对象,则重新抛出该对象很重要,因为这样该线程才会真正终止。

     对其它为捕获的异常做出反应的顶级错误处理不会打印输出消息,或者另外通知应用程序为捕获到的已换成那个是否为ThreadDeath的一个实例。

5、结束程序的执行

      每个应用程序都从执行main的线程开始的,如果应用程序没有创建任何其它的线程,那么main方法返回时,应用程序就结束了,但是如果应用程序创建了其它线程,就要根据线程的类型分情况来考虑了。

      线程一般分为两种:用户线程和守护线程。用户线程的存在可以使应用程序保持运行状态,而守护线程则不会。当最后一个用户线程结束时,所有守护线程都会被终止,应用程序也随之结束。守护线程的终止,很像调用destroy所产生的终止,事发突然,没有几乎做任何清除,所以应该考虑清楚,用守护线程执行哪种类型的任务。使用Thread.setDaemon(true)可以把线程标记为守护线程。默认情况下,线程的守护状态继承自创建它的线程。

     一般main线程是程序运行时第一个启动的线程,称作为初始线程。如果希望应用程序在初始线程消亡后就退出,就可以把所创建处理的线程都标记为守护线程。我们也可以通过调用System,或者Runtime的exit方法来强制应用程序结束,这个方法将终止Java虚拟机的当前执行过程。

你可能感兴趣的:(编程,Java,thread,工作,虚拟机)