Concurrency: Basic Threading (3)

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()中可以访问InnerThread1countDown

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. 在方法内创建线程

  下面的ThreadMethodrunTask()中创建并并开始线程。如果创建线程只是为了做一些辅助性的工作,这种方式比在构造器中创建并开始线程更加合适。

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();
        }
    }
}

  9.39.7中的5个例子可以通过如下的方式运行:

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
*/

Sleepersleep()可以被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通过ThreadsetUncaughtExceptionHandler(),为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(如果有的话)。