并发编程使咱们能够将程序划分为多个分离的、独立运行的任务。经过使用多线程机制,这些独立的任务(也被称为子任务)中的每个都将由「执行线程」来驱动。一个线程就是在进程中的一个单一的顺序控制流。java
在使用线程时,CPU 将轮流给每一个任务分配其占用时间。每一个任务都以为本身在一直占用 CPU,但事实上 CPU 时间是划分红片断分配给了全部任务(此为单 CPU 状况,多 CPU 确实是同时执行)。编程
使用线程机制是一种创建透明的、可扩展的程序的方法,若是程序运行得太慢,为机器增添一个 CPU 就能很容易地加快程序的运行速度。多任务和多线程每每是使用多处理器系统的最合理的方式。大数据的分布式扩展思想与之相似,当程序性能不行时,能够经过扩展集群提升程序并发度提升性能,可是不准修改代码。多线程
线程能够驱动任务,而描述任务须要必定的方式,java 中建议的方式是实现 Runnable 接口,其次是继承 Thread 类。如下是两种方式的代码实现:并发
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..."); } }
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..."); } }
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_PRIORITY
,NORM_PRIORITY
,MIN_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()
方法处理未捕获的异常。这一般是在一组线程以一样方式处理未捕获异常时使用,若不一样的线程须要有不一样的异常处理方式,则最好在线程内部单独处理。