Concurrency - The Java platform has APIs to help you develop multi-threaded programs.
java平台有api可以帮助你开发多线程程序。
计算机用户理所当然认为他们的系统能够在同一时间做多件事情。他们表示他们可以持续做文字处理工作的同时,其它应用可以下载文件、管理打印队列和音频流。甚至一个应用经常在同一个时刻做多件事情。例如,音频流处理程序必须同时读取数码音频、管理回放和更新展示。甚至一个文字处理其要随时响应键盘和鼠标时间,不管它忙于格式化文本或者更新现实。能够做的这些事情的软件都被认为是并发软件。
基于java编程语言和java类库,java平台从开始就被设计为支持并发编程。从5.0版本开始,java平台包含高级并发api。这节将介绍这个支持并发平台和概述java.util.concurrent包中高级并发apis
一、进程和线程(processes and threads)
在并发编程中,有两种基础单元:进程和线程。在java编程语言中,并发编程更多涉及线程。然后进程也是同样重要的。
一个计算机系统正常有系统活动的进程和线程。即使系统仅有一个内核,在同一个时刻仅有一个线程执行,这是真是存在的。操作系统中,被进程和线程共享的一个单独内核的处理时间被称为时间片。
计算机系统拥有多处理器或者多核处理器已经越来越普遍了,这个大大增强系统编发进程和线程并,发处理能力,但是即使没有多核处理器,在单机系统中并发还是可能存在的。
1.进程
每个进程都有自己的执行环境,有自己完整、私有的运行时资源;特别的,每个进程拥有自己的内存空间。
进程常常被认为是程序或者应用,然而,被人们认为单独的应用程序常常是一系列的互相协调合作的进程的集合。为了管理进程间的通信,大部分操作系统支持进进程间通信,如管道、socket。进程间通信不仅仅支持一个系统间通信,它还支持不同系统间的。
大部分java虚拟机作为一个进程实现的。每个java应用可以使用ProcessBuilder对象创建额外的进程。多进程应用不在本节讨论范围内。
2.线程
线程有时也被称为轻量级进程,进程和线程都提供一个执行环境,但是,相对创建一个新的进程,创建一个新的线程需要更少的资源。
线程存在进程中,每个进程都至少有一个。线程共享进程资源,包括内存和开打的文件。这提高了效率,但是也有潜在的问题和通信。
多线程执行是java平台的一个重要特性,每一个应用至少有一个或者多个线程,你可以统计一个在做内存管理和信号处理的线程。从应用程序视角看,你开始于一个被称为主线程的线程,这个线程能够创建额外的线程。我们将在下一节演示。
二、线程对象(thread objects)
每个线程都是Thread类的一个实例。有两种基本的方法使用Thread对象来创建并发应用。
a.为了直接控制线程创建和管理,在应用需要发起一个异步任务时,简单的实例化Thread即可
b.为了在应用空闲时间接管理线程,将应用任务传递给一个执行器即可
这一章将介绍Thread对象的使用,执行器将会在高级并发章节中介绍
1.定义和启动线程
一个创建Thread实例的应用,必须在线程中提供run方法的代码。有两种实现方式:
a.提供一个Runnable对象,Runnable接口定义一个简单的run方法,意味着执行代码包含在Thread中,这个Runnable对象被传给Thread构造器,如HelloRunnable例子:
public class HelloRunnable implements Runnable{ public void run(){ System.out.println("Hello from a thread!"); } public static void main(String args[]){ <span style="white-space:pre"> </span>(new Thread(new HelloRunnable())).start(); } }b.继承Thread类,Thread类本身实现了Runnable接口,尽管它的run方法不做任何事情。一个应用可以继承Thread,并重写run方法,如HelloThread例子:
public class HelloThread extends Thread{ public void run(){ System.out.println("hello from a thread!"); } public static void main(String args[]){ <span style="white-space:pre"> </span>(new HelloThread()).start(); } }注意:两种方式均通过Thread.start方法启动
你该使用哪种方式了?对于第一种,使用Runnable对象,更加普遍,因为Runnable对象可以继承不是Thread的类。第二种方式虽然更加容易使用,但是你的任务类必须是Thread的子类。这种将Runnable任务和线程对象分离的执行任务是本章的重点,这种方式不仅容易扩展,这也是后续高级线程管理篇覆盖的内容。
Thread类定义了很多有用方法来空间线程管理,这包括静态方法,提供信息或者影响线程调用方法的状态。从管理线程和线程对象的线程调用其它线程方法。我们将在接下来的章节中测试这些方法。
2.使用sleep暂停线程
Thread类的sleep方法能够将当前线程在特定的一个时间段挂起,这是一种有效的将处理器时间空闲出来让给其它线程使用的方式。sleep方法也能够 用于设定间隔时间,如接下来的例子所示,等待其它有时间需求的线程执行。SimpleThread 例子所示。
两个重载的sleep方法被提供:一种是按照毫秒,另外一种是按照纳秒。然而,休眠时间不能保证准确,因为它们受底层操作系统设备的影响,同时,休眠期间也可以被中断,我们将在下一章中见到,无论如何,你都不能认为调用sleep方法就能够按照设定的准确时间挂起线程。
SleepMessage例子使用sleep每隔4秒打印消息:
<span style="font-family: monospace; white-space: pre; background-color: rgb(240, 240, 240);">for(int i = 0;i < importantInfo.lenght; i++){ try{Thread.sleep(4000)}catch(InturerruptException e){return}System.out.println(importantInfo[i]);}</span>
注意:main方法声明抛出InterruptException。这是由sleep方法抛出的一个异常,当另外一个线程中断当前休眠状态的线程。如果应用没有定义其它线程来引发中断,大可不必担心捕获InterruptException。
3.中断
中断表明一个线程应该停止当前处理事情而取做其它事情。一个程序怎么响应一个中断可以准确的由程序员决定,而终止线程是一种普遍的做法。这节将重点说明中断使用方法。
一个线程可以通过调用Thread对象中的interrupt方法发送一个中断给需要被中断的线程,对于中断机制正常的机器,被中断的线程必须支持被中断。
a.支持中断(Supporting Interruption)
一个线程怎样支持自己的中断了?这依赖于它当前正在做的事情。如果这线程频繁调用那些抛出InterruptException方法,它将在捕获到中断异常后从run方法中返回。例如,SleepMessage实例中,在一个Runnable对象run方法中循环消息体,它将被修改成如下以便支持中断:
</pre><pre name="code" class="java">for(int i = 0;i < importantInfo.lenght; i++){ try{ Thread.sleep(4000); } catch (InturerruptException e) return; System.out.println(importantInfo[i]); }
许多抛出中断异常的方法,如sleep,被设计为当接受到一个中断后,可以取消他们当前操作并立即返回。假如一个线程长时间没有调用声明抛出中断异常的方法那将会怎样了?那它将必须周期性的调用Thread.interrupted,如果它接收到一个中断,这个方法会返回true,如:
for(int i = 0; i < inputs.length; i++){ heavyCrunch(inputs[i]); if(Thread.interrupted()){ return; } }
中断机制是由内部中断状态标志实现的 。调用Thread.interrupt设置这个标志,当一个线程通过Thread.interrupted方法检验一个中断时,中断状态被清除,非静态方法isInterrupted方法,用于一个线程查询另外一个线程的中断标志,不会改变中断状态标志。
根据约定,任何通过抛出中断异常退出的方法都会清除中断状态。然而,中断状态会被立即重新设置,在其它线程调用interrupt方法是。
4.关联(join)
join方法允许一个线程等待另外一个线程,知道这个线程完成任务。如果t是一个正在执行join的线程对象,会将当前线程暂停执行直到t线程终止。重载join方法允许程序员定义线程等待一段时间。然而,由于sleep、join是依赖于操作系统的,所以你不要希望join方法能够准确按照预期等待。
sleep和join通过抛出中断异常来响应中断。
5.简单的线程例子
接下来的例子会将本章提到的概念融合在一块。SimpleThreads包含两个线程。第一个是每个java应用都有的main主线程,这个主线程从Runnable对象创建一个新的线程MessageLoop,并等待它完成任务。如果MessageLoop耗费太长时间来结束,这个主线程将会中断它。
这个MessageLoop线程打印一系列信息,如果在完成打印之前被中断,这MessageLoop线程将打印一条信息并退出。
public class SimpleThread { // Display a message, preceded by // the name of the current thread static void threadMessage(String message) { String threadName = Thread.currentThread().getName(); System.out.format("%s: %s%n", threadName, message); } private static class MessageLoop implements Runnable { public void run() { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; try { for (int i = 0; i < importantInfo.length; i++) { // Pause for 4 seconds Thread.sleep(4000); // Print a message threadMessage(importantInfo[i]); } } catch (InterruptedException e) { threadMessage("I wasn't done!"); } } } public static void main(String args[]) throws InterruptedException { // Delay, in milliseconds before // we interrupt MessageLoop // thread (default one hour). long patience = 1000 * 60 * 60; // If command line argument // present, gives patience // in seconds. if (args.length > 0) { try { patience = Long.parseLong(args[0]) * 1000; } catch (NumberFormatException e) { System.err.println("Argument must be an integer."); System.exit(1); } } threadMessage("Starting MessageLoop thread"); long startTime = System.currentTimeMillis(); Thread t = new Thread(new MessageLoop()); t.start(); threadMessage("Waiting for MessageLoop thread to finish"); // loop until MessageLoop // thread exits while (t.isAlive()) { threadMessage("Still waiting..."); // Wait maximum of 1 second // for MessageLoop thread // to finish. t.join(1000); if (((System.currentTimeMillis() - startTime) > patience) && t.isAlive()) { threadMessage("Tired of waiting!"); t.interrupt(); // Shouldn't be long now // -- wait indefinitely t.join(); } } threadMessage("Finally!"); } }
四、活跃度
五、保护块
六、不变对象
七、高级并发
八、深入阅读
九、问题和练习