Concurrency: Basic Threading (3)
Contents
9. Coding variations
9.1. 直接继承Thread
创建任务除了通过实现Runnable
接口,也可以通过直接继承Thread
的方式来实现。
public class SimpleThread extends Thread { private int countDown = 5; private static int threadCount = 0; public SimpleThread() { // Store the thread name: super(Integer.toString(++threadCount)); start(); } public String toString() { return "#" + getName() + "(" + countDown + "), "; } public void run() { while(true) { System.out.print(this); if(--countDown == 0) return; } } public static void main(String[] args) { for(int i = 0; i < 5; i++) new SimpleThread(); } } /* Output #1(5), #4(5), #4(4), #4(3), #5(5), #2(5), #3(5), #2(4), #2(3), #2(2), #2(1), #5(4), #4(2), #4(1), #1(4), #5(3), #3(4), #5(2), #1(3), #5(1), #3(3), #3(2), #3(1), #1(2), #1(1), */
9.2. 自管理Runnable
还有一种自管理Runnable
的用法,由实现Runnable
的类自己创建并开始线程。
public class SelfManaged implements Runnable { private int countDown = 5; private Thread t = new Thread(this); public SelfManaged() { t.start(); } public String toString() { return Thread.currentThread().getName() + "(" + countDown + "), "; } public void run() { while(true) { System.out.print(this); if(--countDown == 0) return; } } public static void main(String[] args) { for(int i = 0; i < 5; i++) new SelfManaged(); } } /* Output Thread-0(5), Thread-4(5), Thread-3(5), Thread-2(5), Thread-1(5), Thread-2(4), Thread-3(4), Thread-4(4), Thread-0(4), Thread-4(3), Thread-3(3), Thread-2(3), Thread-1(4), Thread-2(2), Thread-3(2), Thread-4(2), Thread-0(3), Thread-4(1), Thread-3(1), Thread-2(1), Thread-1(3), Thread-1(2), Thread-0(2), Thread-1(1), Thread-0(1), */
通过实现Runnable
来创建任务的好处是,可以同时继承其他类。
上面的两个简单的例子都在构造器中调用start()
开始线程。对于更加复杂的情况,在构造器中调用start()
可能会带来问题:调用start()
时任务已经在新的线程上开始执行,但构造器可能还没有执行结束,这时任务就有可能访问还没有初始化完成的资源。应尽量使用Executors
而不要手动创建Thread
。
9.3. 内部类继承Thread
使用内部类继承Thread
的方式,可以在任务中访问外部成员。
class InnerThread1 { private int countDown = 5; private Inner inner; private class Inner extends Thread { Inner(String name) { super(name); start(); } public void run() { try { while(true) { System.out.println(this); if(--countDown == 0) return; sleep(10); } } catch(InterruptedException e) { System.out.println("interrupted"); } } public String toString() { return getName() + ": " + countDown; } } public InnerThread1(String name) { inner = new Inner(name); } }
这里InnerThread1
的内部类Inner
继承Thread
,在其run()
中可以访问InnerThread1
的countDown
。
9.4. 匿名内部类继承Thread
如果只是为了获得多线程的能力,继承Thread
的内部类的名字并不重要,可以使用匿名内部类。
class InnerThread2 { private int countDown = 5; private Thread t; public InnerThread2(String name) { t = new Thread(name) { public void run() { try { while(true) { System.out.println(this); if(--countDown == 0) return; sleep(10); } } catch(InterruptedException e) { System.out.println("sleep() interrupted"); } } public String toString() { return getName() + ": " + countDown; } }; t.start(); } }
9.5. 内部类实现Runnable
下面的InnerRunnable1
使用内部类实现Runnable
。
import java.util.concurrent.TimeUnit; class InnerRunnable1 { private int countDown = 5; private Inner inner; private class Inner implements Runnable { Thread t; Inner(String name) { t = new Thread(this, name); t.start(); } public void run() { try { while(true) { System.out.println(this); if(--countDown == 0) return; TimeUnit.MILLISECONDS.sleep(10); } } catch(InterruptedException e) { System.out.println("sleep() interrupted"); } } public String toString() { return t.getName() + ": " + countDown; } } public InnerRunnable1(String name) { inner = new Inner(name); } }
9.6. 匿名内部类实现Runnable
下面的InnerRunnable2
使用匿名内部类实现Runnable
。
import java.util.concurrent.TimeUnit; class InnerRunnable2 { private int countDown = 5; private Thread t; public InnerRunnable2(String name) { t = new Thread(new Runnable() { public void run() { try { while(true) { System.out.println(this); if(--countDown == 0) return; TimeUnit.MILLISECONDS.sleep(10); } } catch(InterruptedException e) { System.out.println("sleep() interrupted"); } } public String toString() { return Thread.currentThread().getName() + ": " + countDown; } }, name); t.start(); } }
9.7. 在方法内创建线程
下面的ThreadMethod
在runTask()
中创建并并开始线程。如果创建线程只是为了做一些辅助性的工作,这种方式比在构造器中创建并开始线程更加合适。
class ThreadMethod { private int countDown = 5; private Thread t; private String name; public ThreadMethod(String name) { this.name = name; } public void runTask() { if(t == null) { t = new Thread(name) { public void run() { try { while(true) { System.out.println(this); if(--countDown == 0) return; sleep(10); } } catch(InterruptedException e) { System.out.println("sleep() interrupted"); } } public String toString() { return getName() + ": " + countDown; } }; t.start(); } } }
public class ThreadVariations { public static void main(String[] args) { new InnerThread1("InnerThread1"); new InnerThread2("InnerThread2"); new InnerRunnable1("InnerRunnable1"); new InnerRunnable2("InnerRunnable2"); new ThreadMethod("ThreadMethod").runTask(); } }
10. Joining a Thread
在A线程中调用B线程的join()
方法,A线程会被挂起,直到B线程完成后(isAlive()
是false
),A线程才会继续运行。可以在调用join()
时加上一个表示超时的参数,如果B线程没有在时限内完成,join()
就会直接返回,使A线程得以继续运行。interrupt()
也可以打断join()
。
class Sleeper extends Thread { private int duration; public Sleeper(String name, int sleepTime) { super(name); duration = sleepTime; start(); } public void run() { try { sleep(duration); } catch(InterruptedException e) { System.out.println(getName() + " was interrupted. " + "isInterrupted(): " + isInterrupted()); return; } System.out.println(getName() + " has awakened"); } } class Joiner extends Thread { private Sleeper sleeper; public Joiner(String name, Sleeper sleeper) { super(name); this.sleeper = sleeper; start(); } public void run() { try { sleeper.join(); } catch(InterruptedException e) { System.out.println("Interrupted"); } System.out.println(getName() + " join completed"); } } public class Joining { public static void main(String[] args) { Sleeper sleepy = new Sleeper("Sleepy", 1500), grumpy = new Sleeper("Grumpy", 1500); Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc", grumpy); grumpy.interrupt(); } } /* Output Grumpy was interrupted. isInterrupted(): false Doc join completed Sleepy has awakened Dopey join completed */
Sleeper
的sleep()
可以被interrupt()
打断。interrupt()
会设置一个标志位,指示线程被打断,interrupt()
产生的异常被捕获后,这个标志位就会被清除。从打印可以看到,Sleeper
捕获InterruptedException
后,查询isInterrupted()
的值为false
。之后很快就会出现打印:
Doc join completed
因为doc
对应的grumpy
已经被打断。
如果不打断grumpy
,而是打断doc
,则可以看到doc
能够被成功打断并立即完成:
public class Joining { public static void main(String[] args) { Sleeper sleepy = new Sleeper("Sleepy", 1500), grumpy = new Sleeper("Grumpy", 1500); Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc", grumpy); doc.interrupt(); } } /* Interrupted Doc join completed Grumpy has awakened Sleepy has awakened Dopey join completed */
11. Creating Responsive User Interfaces
多线程的一个重要应用是用来创建响应式的用户界面,通过把耗时的业务放置到单独的线程去运行,前台界面就可以及时地响应用户的交互。
class UnresponsiveUI { private volatile double d = 1; public UnresponsiveUI() throws Exception { while(d > 0) d = d + (Math.PI + Math.E) / d; System.in.read(); // Never gets here } } public class ResponsiveUI extends Thread { private static volatile double d = 1; public ResponsiveUI() { setDaemon(true); start(); } public void run() { while(true) { d = d + (Math.PI + Math.E) / d; } } public static void main(String[] args) throws Exception { //! new UnresponsiveUI(); // Must kill this process new ResponsiveUI(); System.in.read(); System.out.println(d); // Shows progress } }
12. Thread Groups
线程组持有线程的集合,这里直接引用Thinking in Java的原话:
The value of thread groups can be summed up by a quote from Joshua Bloch, the software architect who, while he was at Sun, fixed and greatly improved the Java collections library in JDK 1.2 (among other contributions):
“Thread groups are best viewed as an unsuccessful experiment, and you may simply
ignore their existence.”
13. Catching Exceptions
异常不会跨线程传递,如果有未被处理的异常从run()
中逃了出来,该异常会被传递到控制台。在Java SE 5之前需要使用线程组来捕获异常,而在Java SE 5中则可以使用Executors
,不必直接接触线程组。
下面的代码在run()
中抛出异常:
import java.util.concurrent.*; public class ExceptionThread implements Runnable { public void run() { throw new RuntimeException(); } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new ExceptionThread()); } }
该异常未被处理,会在控制台打印出来:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
直接在main()中加上try-catch,依然无法捕获异常:
public class NaiveExceptionHandling { public static void main(String[] args) { try { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new ExceptionThread()); } catch(RuntimeException ue) { // This statement will NOT execute! System.out.println("Exception has been handled!"); } } }
同样的异常仍旧会在控制台上打印出来:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
为了解决这个问题,需要改变Executor
产生线程的方式。Java SE 5中新加入的Thread.UncaughtExceptionHandler
接口允许为Thread
对象添加异常处理程序。当线程中出现未捕获的异常、即将终止时,Thread.UncaughtExceptionHandler.uncaughtException()
会被自动调用。
这里使用自定义ThreadFactory
的方式,为其生成的线程附加上Thread.UncaughtExceptionHandler
,然后把ThreadFactory
传递给Executors
,得到ExecutorService
。
import java.util.concurrent.*; class ExceptionThread2 implements Runnable { public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t); System.out.println("eh = " + t.getUncaughtExceptionHandler()); throw new RuntimeException(); } } class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } } class HandlerThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { System.out.println(this + " creating new Thread"); Thread t = new Thread(r); System.out.println("created " + t); t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); System.out.println("eh = " + t.getUncaughtExceptionHandler()); return t; } } public class CaptureUncaughtException { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool( new HandlerThreadFactory()); exec.execute(new ExceptionThread2()); } } /* Output com.company.exceptions.HandlerThreadFactory@7f31245a creating new Thread created Thread[Thread-0,5,main] eh = com.company.exceptions.MyUncaughtExceptionHandler@6d6f6e28 run() by Thread[Thread-0,5,main] eh = com.company.exceptions.MyUncaughtExceptionHandler@6d6f6e28 com.company.exceptions.HandlerThreadFactory@7f31245a creating new Thread created Thread[Thread-1,5,main] eh = com.company.exceptions.MyUncaughtExceptionHandler@c6f6863 caught java.lang.RuntimeException */
MyUncaughtExceptionHandler
实现了Thread.UncaughtExceptionHandler
,这里只是将异常打印出来。HandlerThreadFactory
通过Thread
的setUncaughtExceptionHandler()
,为Thread
加上MyUncaughtExceptionHandler
用来处理异常。从打印可见,ExceptionThread2
抛出的未捕获的异常被MyUncaughtExceptionHandler
处理了。
可以通过Thread的setDefaultUncaughtExceptionHandler()
方法为Thread添加默认的异常处理:
import java.util.concurrent.*; public class SettingDefaultHandler { public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler( new MyUncaughtExceptionHandler()); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new ExceptionThread()); } } /* Output caught java.lang.RuntimeException */
如果没有为线程单独设置UncaughtExceptionHandler,则会应用默认的UncaughtExceptionHandler(如果有的话)。