花园有多个入口,希望统计花园每天的总人数。
public class OrnamentalGarden { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<5;i++){ exec.execute(new Entrance(i)); } TimeUnit.MILLISECONDS.sleep(3000); Entrance.cancel(); exec.shutdown(); if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS)){ System.out.println("some tasks were no terminated!"); } System.out.println("Total:"+Entrance.sumEntrance()); } } // //创建计数器 class Count{ //记录总的数目 private int count=0; private Random rand = new Random(47); public synchronized int increment(){ int tmp = count; //使用随机数,即有一半的时间当前线程会让步,以此增加出错的可能性。 if(rand.nextBoolean()) Thread.yield(); return (count = ++tmp); } 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; //某一个入口的id private final int id; //入口是否关闭 private static volatile boolean canceled=false; public static void cancel(){ canceled = true; } public Entrance(int id){ this.id = id; entrances.add(this); } @Override public void run() { while(!canceled){ synchronized(this){ ++number; } System.out.println("Entrance "+id+": "+getValue()+": Total:"+count.increment()); try{ TimeUnit.MILLISECONDS.sleep(100); }catch(InterruptedException e){ System.out.println("sleep is interrupted!"); } } System.out.println("stopping Entrance "+id+": "+getValue()); } //获得当前入口的人数 public synchronized int getValue(){ return number; } //获得所有入口的总人数,由count计数器给出 public static int getTotal(){ return count.value(); } //统计每个入口的总人数 public static int sumEntrance(){ int sum=0; for(Entrance e:entrances){ sum +=e.getValue(); } return sum; } }
在阻塞时终结
上面的例子中,Entrance.run()循环中包含sleep()的调用。sleep()终将被唤醒,任务又返回循环开始的地方判断canceled标志,从而决定是否跳出循环。但是,sleep()下,它使任务变为阻塞,而我们有时候需要终止被阻塞的任务。上面的代码明显无法满足我们的需求。下面的中断可以才能解决。
中断
在Runnable.run()方法的中间打断它,与等待run()方法对canceled标志进行检查相比,要棘手的多。
在任务的run()方法中间打断它,很像是抛出一个异常,因此java线程中这种类型的异常中断用到了异常。为了在以这种方式终止任务时,返回良好状态,需要仔细编写catch语句以正确清除所有事物。
为了调用interrupt(),必须持有Thread对象。而新的concurrent类库为了避免直接对Thread对象操作,转而使用Executor来执行。
Executor调用shutdownNow(),那么它将发送一个interrupt()给它启动的所有线程。
然而,有时候会想中断某个单一任务,此时应该使用submit()启动任务,该方法可以返回一个Funture<?>对象,使用该对象的cancel()方法可以中断特定的任务。它也是在指定线程上调用interrupt()方法。
当在线程上调用interrupt()时,中断发生的唯一时刻是在任务要进入到阻塞操作或者已经在阻塞操作内部时。
public void interrupt()
Interrupts this thread.
Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.
If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.
If this thread is blocked in an I/O operation upon an interruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.
If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.
If none of the previous conditions hold then this thread's interrupt status will be set.
Interrupting a thread that is not alive need not have any effect.
检查中断
当run()方法不处在阻塞时,interrupt()方法便无法停止任务。以下为第二种方法。
当调用interrupt()方法时会产生中断状态,对中断状态进行检查即可。在run方法中,我们应该同时处理阻塞和非阻塞的两种情况。
public class CheckInterrupt { public static void main(String[] args) throws InterruptedException { Task ts = new Task(); Thread t = new Thread(ts); t.start(); Thread.sleep(1000); t.interrupt(); } } class Task implements Runnable{ @Override public void run() { while(!Thread.currentThread().isInterrupted()){ try{ System.out.println("当前线程:"+Thread.currentThread().getId()+" 正在运行!"); Thread.sleep(100); }catch(InterruptedException e){ System.out.println("当前线程:"+Thread.currentThread().getId()+" 接收到中断请求!"); Thread.currentThread().interrupt(); } } } }
上面的例子中对阻塞和非阻塞都进行了处理,因此调用interrupt()都可以停止任务的运行。当接收到InterruptedException异常时,会清除Thread.currentThread().isInterrupted()标志,因此若要跳出while循环,必须再次标志上Thread.currentThread().isInterrupted()的中断状态,使用Thread.currentThread().interrupt()即可。