java多线程系列----------- 终结任务(一)

能有此文十分感谢《Java编程思想》一书及其作者Bruce Eckel!

        首先,先观察一个示例,它不仅演示了终止问题,而且还是一个资源共享的示例。

一、装饰性花园

        在这个仿真程序中,花园委员会希望了解每天通过多个大门进入公园的总人数。每个大门都有一个某种形式的计数器,并且任何一个计数器值递增时,表示公园中的总人数的共享计数器的值也会递增。

import java.util.concurrent.*;
import java.util.*;

class Count {
	private int count = 0;
	private Random rand = new Random(47);

	// Remove the synchronized keyword to see counting fail:
	public synchronized int increment() {
		int temp = count;
		if (rand.nextBoolean()) // Yield half the time
			Thread.yield();
		return (count = ++temp);
	}

	public synchronized int value() {
		return count;
	}
}

class Entrance implements Runnable {
	private static Count count = new Count();
	private static List<Entrance> entrances = new ArrayList<Entrance>();
	private int number = 0;
	// Doesn't need synchronization to read:
	private final int id;
	private static volatile boolean canceled = false;

	// Atomic operation on a volatile field:
	public static void cancel() {
		canceled = true;
	}

	public Entrance(int id) {
		this.id = id;
		// Keep this task in a list. Also prevents
		// garbage collection of dead tasks:
		entrances.add(this);
	}

	public void run() {
		while (!canceled) {
			synchronized (this) {
				++number;
			}
			System.out.println(this + " Total: " + count.increment());
			try {
				TimeUnit.MILLISECONDS.sleep(100);
			} catch (InterruptedException e) {
				System.out.println("sleep interrupted");
			}
		}
		System.out.println("Stopping " + this);
	}

	public synchronized int getValue() {
		return number;
	}

	public String toString() {
		return "Entrance " + id + ": " + getValue();
	}

	public static int getTotalCount() {
		return count.value();
	}

	public static int sumEntrances() {
		int sum = 0;
		for (Entrance entrance : entrances)
			sum += entrance.getValue();
		return sum;
	}
}

public class OrnamentalGarden {
	public static void main(String[] args) throws Exception {
		ExecutorService exec = Executors.newCachedThreadPool();
		for (int i = 0; i < 5; i++)
			exec.execute(new Entrance(i));
		// Run for a while, then stop and collect the data:
		TimeUnit.SECONDS.sleep(3);
		Entrance.cancel();
		exec.shutdown();
		if (!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
			System.out.println("Some tasks were not terminated!");
		System.out.println("Total: " + Entrance.getTotalCount());
		System.out.println("Sum of Entrances: " + Entrance.sumEntrances());
	}
}
        这里使用单个的Count对象来跟踪花园参观者的主计数值,并且将其当作Entrance类中的一个静态域进行存储。Count.increment()和Count.value()都是synchronized的,用来控制对count域的访问。increment()方法使用了Random对象,目的是从把count读取到temp中,到递增temp并将其存储回count的这段时间里,有大约一半时间产生让步。如果将increment()上的synchronized关键字注释掉,那么这个程序就会崩溃,因为过个任务将同时访问并修改count(yield()会使问题更快地发生)。

        每个Entrance任务都维护着一个本地值number,它包含通过某个特定入口进入的参观者的数量。这提供了对count对象的双重检查,以确保其记录的参观者数量是正确的。Entrance.run()只是递增number和count对象,然后休眠100毫秒。

        因为Entrance.canceled是一个volatile布尔标志,而它只会被读取和赋值(不会与其他域组合一起被读取),所以不需要同步对其访问,就可以安全地操作它。如果你对诸如此类的情况有任何疑虑,那么最好使用synchronized。

        这个程序在以稳定的方式关闭所有事物方面还有一些小麻烦,其部分原因是为了说明在终止多线程程序时你必须相当小心,而另一部分原因是为了演示interrupt()的值。

        在3秒钟之后,main()向Entrance发送static cancel()消息,然后调用exec对象的shutdown()方法。ExecutorService.awaitTermination()等待每个任务结束,如果所有的任务在超时时间达到之前全部结束,则返回true,否则返回false。尽管这会导致每个任务都退出其run()方法,并因此作为任务而终止,但是Entrance对象仍旧是有效的,因为在构造器中,每个Entrance对象都存储在称为entrances的静态List<Entrance>中,因此,sumEntrances()仍旧可以作用于这些有效的Entrance对象。

        当这个程序运行时,你将看到,在人们通过大门时,将显示总人数和通过每个入口的人数。如果移除Count.increment()上面的synchronized,将会看到总人数与你期望的有差异,每个入口统计的人数将与count中的值不同。只要用互斥来同步对Count的访问,问题就可以解决了。

二、在阻塞时终结

        前面示例中的Entrance.run()在其循环中包含了对sleep()的调用。我们知道sleep()最终将唤醒,而任务也将返回循环的开始部分,去检查canceled标志,从而决定是否跳出循环。但是,sleep()一种情况,它使任务从执行状态变为阻塞状态,而有时必须终止被阻塞的任务。

线程状态

一个线程可以处于一下四种状态之一:

1)新建(new):当线程被创建时,它只会短暂地处于这种状态,此时它已经分配了必须的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行或阻塞状态。

2)就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片段给线程,它就可以运行,这不同于死亡和阻塞状态。

3)阻塞(Blocked):线程能够运行,但有某个条件阻止它运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给任何CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。

4)死亡(Dead):处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。

进入阻塞状态

        一个任务进入阻塞状态可能有如下原因:

1)通过调用sleep()使任务进入休眠状态,在这种情况下,任务在指定时间内不会运行。

2)通过调用wait()使线程挂起。直到线程得到了notify()或notifyAll()消息(或者在Java SE5的java.util.concurrent类库中等价的signal()或signalAll()消息),线程才会进入就绪状态。

3)在任务等待某个输入/输出完成

4)任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁。

        在较早的代码中,也可能会看到用suspend()和resume()来阻塞和唤醒线程,但是在现代java中这些方法被废止了(因为可能导致死锁)。stop()方法也已经被废止了,因为它不释放线程获得的锁,并且如果线程处于不一致的状态(受损状态),其他任务可以在这种状态下浏览并修改它们。

        现在我们需要查看的问题是:有时你希望能够终止处于阻塞状态的任务。如果对处于阻塞状态的任务,你不能等待其到达代码中可以检查其状态值的某一点,因而决定让它主动地终止,那么你就必须强制这个任务跳出阻塞状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值