《Java 编程思想》读书笔记之并发(二)

基本的线程机制

并发编程使咱们能够将程序划分为多个分离的、独立运行的任务。经过使用多线程机制,这些独立的任务(也被称为子任务)中的每个都将由「执行线程」来驱动。一个线程就是在进程中的一个单一的顺序控制流。java

在使用线程时,CPU 将轮流给每一个任务分配其占用时间。每一个任务都以为本身在一直占用 CPU,但事实上 CPU 时间是划分红片断分配给了全部任务(此为单 CPU 状况,多 CPU 确实是同时执行)。编程

使用线程机制是一种创建透明的、可扩展的程序的方法,若是程序运行得太慢,为机器增添一个 CPU 就能很容易地加快程序的运行速度。多任务和多线程每每是使用多处理器系统的最合理的方式。大数据的分布式扩展思想与之相似,当程序性能不行时,能够经过扩展集群提升程序并发度提升性能,可是不准修改代码。多线程

定义任务

线程能够驱动任务,而描述任务须要必定的方式,java 中建议的方式是实现 Runnable 接口,其次是继承 Thread 类。如下是两种方式的代码实现:并发

Runnable 接口实现

public class MyTask implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread() + ": running...");
    }

    public static void main(String[] args) {
        Thread t = new Thread(new MyTask());
        t.start();
        System.out.println(Thread.currentThread() + ": running...");
    }

}

Thread 继承实现

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread() + ": running...");
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println(Thread.currentThread() + ": running...");
    }

}

使用 Executor

Java SE5 的 java.util.concurrent 包中的执行器(Executor)将为你管理 Thread 对象,从而简化了并发编程。其实就是 Java 的线程池,它大大的减小的对于线程的管理,包括线程的建立、销毁等,而且它还能复用已经建立的线程对象,减小因为反复建立线程引发的开销,即节省了资源,同时也提升了程序的运行效率。这部份内容比较重要,以后会单独开一篇介绍,此处就介绍到这。分布式

从任务中产生返回值

实现 Runnable 接口只能执行任务,没法得到任务的返回值。若是但愿得到返回值,则应该实现 Callable 接口,而且应该使用 ExecutorService.submit() 方法调用它。下面是一个示例:ide

public class MyCallableTask implements Callable<String> {

    private int id;

    public MyCallableTask(int id) {
        this.id = id;
    }

    @Override
    public String call() throws Exception {
        return "Result : " + Thread.currentThread() + ": " + id;
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            futures.add(exec.submit(new MyCallableTask(i)));
        }

        for (Future<String> future : futures) {
            try {
                // 调用 future 方法会致使线程阻塞,直到 future 对应的线程执行完毕
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                exec.shutdown();
            }
        }
    }

}

submit() 方法会产生 Future 对象,而且使用泛型的方式对返回值类型进行了定义。调用 Future.get() 方法,会致使线程阻塞,直到被调用的线程执行完毕返回结果,固然 java 也提供了设置超时时间的 get() 方法,防止线程一直阻塞,或者你也能够调用 Future 的 isDone() 方法预先判断线程是否执行完毕,再调用 get() 获取返回值。性能

休眠

「休眠」就是使任务停止执行指定的时间。在 Java 中能够经过Thread.sleep()方法来实现,JDK 1.6 以后推荐使用TimeUnit来实现任务的休眠。大数据

sleep()方法的调用会抛出InterruptedException异常,因为异常不能跨线程传播,所以必须在本地处理任务内部产生的异常。this

线程间虽然能够切换,可是并无固定的顺序可言,所以,若要控制任务执行的顺序,绝对不要寄但愿于线程的调度机制。操作系统

优先级

线程的优先级是用来控制线程的执行频率的,优先级高的线程执行频率高,但这并不会致使优先级低的线程得不到执行,仅仅是下降执行的频率。

在绝大多数时间里,全部线程都应该以默认的优先级运行。试图操纵线程优先级一般是一种错误。——《Java 编程思想》

你能够在一个任务的内部,经过调用Thread.currentThread()来得到对驱动该任务的 Thread 对象的引用。

尽管 JDK 有 10 个优先级,但它与多数操做系统都不能映射得很好。所以在手动指定线程优先级的时候尽可能只使用MAX_PRIORITYNORM_PRIORITYMIN_PRIORITY三种级别。

让步

经过调用Thread.yield()方法可使当前线程主动让出 CPU,同时向系统建议具备「相同优先级」的其余线程能够运行(只是一个建议,没有任何机制保证它必定会被采纳)。所以,对于任何重要的控制或在调整应用时,都不能依赖于yield()

后台线程

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,而且这种线程并不属于程序中不可或缺的部分。——《Java 编程思想》

所以,当全部的非后台线程结束时,程序也就停止了,同时会杀死进程中的全部后台线程。

必须在线程启动以前调用setDeamon()方法,才能把它设置为后台线程。

能够经过调用isDaemon()方法来肯定线程是不是一个后台线程。若是是一个后台线程,那么它建立的任何线程将被自动设置成后台线程。

若是在后台线程中有finally{}语句块,当全部非后台程序执行结束时,后台线程会忽然终止,并不会执行finally{}语句块中的内容,后台线程不会有任何开发者但愿出现的结束确认形式。由于不能以优雅的方式来关闭后台线程,因此它们几乎不是一种好的思想。

加入一个线程

若是某个线程在另外一个线程 t 上调用t.join(),此线程将被挂起,直到目标线程 t 结束才恢复(即t.isAlive()返回为 false)。也能够在调用join()时带上一个超时参数,在目标线程处理超时时join()方法总能返回。

join()方法的调用能够被中断,只要在调用线程上调用interrupt()方法。

捕获异常

因为线程的本质特性,一旦在run()方法中未捕捉异常,那么异常就会向外传播到控制台,除非采起特殊的步骤捕获这种错误的异常。

在 Java SE5 以前,可使用线程组来捕获这些异常(不推荐),在 Java SE5 以后能够用 Executor 来解决这个问题。Executor 容许修改产生线程的方式,容许你在每一个 Thread 对象上都附着一个异常处理器Thread.UncaughtExceptionHandler,此异常处理器会在线程因未捕获的异常而临近死亡时被调用uncaughtException()方法处理未捕获的异常。这一般是在一组线程以一样方式处理未捕获异常时使用,若不一样的线程须要有不一样的异常处理方式,则最好在线程内部单独处理。