文章目录,点击查看正文

  • 并发(Concurrency)
    • 1. 进程与线程
      • 1.2 进程
      • 1.3 线程
    • 2. Thread对象
      • 2.1 定义并启动线程(Defining and Starting a Thread)
      • 2.2 用sleep来暂停执行(Pausing Execution with Sleep)
      • 2.3 中断(Interrupts)
        • 2.3.1 支持中断
        • 2.3.2 中断状态标识
      • 2.4 Joins
      • 2.5 Thread程序例子
    • 3. 同步(Synchronization)
      • 3.1 线程冲突(Thread Interference)
      • 3.2 内存一致性错误(Memory Consistency Errors)
      • 3.3 Synchronized方法
      • 3.4 内在锁与同步(Intrinsic Locks and Synchronization)
        • 3.4.1 同步方法的锁
        • 3.4.2 同步语句
        • 3.4.3 可重入的同步
      • 3.5 原子访问
    • 4. 活跃性
      • 4.1 死锁
      • 4.2 饥饿与活锁
    • 5. 警戒块(Guarded Blocks)
    • 6. 不可变对象(Immutable Objects)
      • 6.1 同步类的例子
      • 6.2 设计Immutable对象的策略
    • 7. 高级并发对象(High Level Concurrency Objects)
      • 7.1 锁对象(Lock Objects)
      • 7.2 Executors
        • 7.2.1 Executor 接口
          • 7.2.1.1 Executor
          • 7.2.1.2 ExecutorService
          • 7.2.1.3 ScheduledExecutorService
        • 7.3 Thread Pools
        • 7.4 Fork/Join
          • 7.4.1 基本用法
          • 7.4.2 图像模糊处理的例子
          • 7.4.3 SE中的实现
      • 7.3 Concurrent Collections
      • 7.4 Atomic Variables
      • 7.5 Concurrent Random Numbers
    • 8. 参考读物/工具

并发(Concurrency)

​ 计算机用户想当然地认为他们地计算机能够在同一时间做多件事情。他们认为当其它应用下载文件,管理打印队列和流媒体的时候,他们的打字机仍然能够继续工作。甚至人们常常期望当个应用在同一时间能够做多件事情。举个例子:流媒体应用必须在同一时间从网络上读取数字音频,解压缩,管理播放/回放,更新屏幕显示。甚至对于打字机来说,无论它重新格式化文本或者更新屏幕显示有多么繁忙,它也总是等待着键盘,鼠标的事件响应。能做这样事情的软件我们叫做并发(Concurrency)软件。

​ Java平台在设计上是从底层向高层支持并发编程的——在语言层面支持基础并发功能+Java并发类库。从Java 5就开始引入一些高级并发API。本文介绍Java支持的基础并发功能,并且概要说明一些由java.util.concurrent包提供的并发高级API。

1. 进程与线程

​ 并发编程中,有两种基本的执行单元:进程,线程。而在Java语言中,并发编程我们更关心的是线程。但是并不是说进程就不重要。

​ 计算机即使只有一个CPU,仍然在一般情况下都会有许多活跃的进程和线程,但是在任意时刻,只会有一个线程在执行。通过操作系统名为时间分片(time slicing)的特性,单核CPU的处理时间在进程和线程之间共享。

​ 现实生活中,计算机有多核(multiple processors or processors with multiple execution cores,多执行内核处理器)是越来越常见了。这很大地加强了进程,线程并发执行地容量。但是并发也能够在单核的简单计算机上发生。

1.2 进程

​ 每个进程都有一个独立(self-contained)的执行环境。每个进程通常有一个完整的,私有的基础运行时资源的集合。特别是,每个进程都有它自己的内存空间。

​ 进程常常被认为就是应用程序(programs or applications)。然而,用户看到的单个应用程序很可能实际上是有多个进程相互协作的。为了促进(facilitate )进程间的通信,大多数的操作系统都支持进程间通信资源(Inter Process Communication (IPC) resources),例如管道(pipes),socket。IPC不仅仅用来在同一系统下的进程间通信,不同系统间也存在IPC。

​ 大多数JVM的实现都是单进程运行。Java引用能够使用 ProcessBuilder 对象创建另一个进程。多进程的应用不在本文的讨论范围内。

1.3 线程

​ 线程也叫轻量级进程(lightweight processes)。线程和进程都提供了执行环境,但是创建一个新的线程比创建一个进程需要的资源更少。

​ 线程存在于进程中——每个进程至少有一个线程。线程共享进程的资源,包括内存,文件资源。这样很有效率,但是存在着线程间通信的问题。

​ 多线程是Java平台的一个重要特性。每个应用都有至少一个线程,或者多个线程——如果你把内存管理,信号处理这类的"系统"线程也算在内的话。但是从程序员的角度来看,你只开启了一个线程——主线程(main thread)。此主线程能够创建多个线程,我们在本文的接下来章节细述。

2. Thread对象

​ 每个线程都有一个与之关联的 Thread 类的实例。有两种使用Thread对象来创建并发程序的基本策略。

  • 直接控制线程的创建/管理。每次应用需要发起一个异步任务的时候,直接实例化一个Thread。
  • 从应用程序抽离线程管理。直接传递线程任务的excuror(执行器?)。(pass the application's tasks to an *executor*

这里我们只讨论第一种——Thread对象的使用。Executors将在之后的高级并发对象章节讨论。

2.1 定义并启动线程(Defining and Starting a Thread)

​ 应用实例化Thread必须要提供此线程要执行的代码,有两种方式:

  • Provide a Runnable object 。传给Thread一个Runnale接口的实现。
  • Subclass Thread 。 声明子类,并实现Runnable,也就是要实现run方法。
// 1. Provide a Runnable object
public class HelloRunnable implements Runnable {public void run() {System.out.println("Hello from a thread!");}public static void main(String args[]) {(new Thread(new HelloRunnable())).start();}
// 2. Subclass Thread
public class HelloThread extends Thread {public void run() {System.out.println("Hello from a thread!");}public static void main(String args[]) {(new HelloThread()).start();}
}

Thread.start是启动线程的操作。

​ 那么问题来了,我们应该使用那种呢?或者说他们的区别是什么呢?

  • 传入Runnable。这种更一般化,因为Runnable对象可以是除了Thread之外的其它类的对象。
  • Thread子类。在简单应用程序中更易用,但是它限制你的任务类必须是一个Thread子类。

​ 本文会着重于第一种Runnable方法,因为它能够将Runnable任务从执行任务的Thread对象中分离出来

​ 这种方法不仅仅在于它的灵活性,它同时也适用于之后我们将讨论到的高级线程管理API

​ 线程类提供了很多方法来管理线程。一些static方法提供{访问/修改}调用此方法的线程的{信息/状态}

2.2 用sleep来暂停执行(Pausing Execution with Sleep)

Thread.sleep导致当前线程暂停执行指定的时间。这是一个有效的手段来使处理器的时间给程序的其它线程或者计算机上其它应用的线程来使用。

​ sleep方法也能被用来定速(pacing)。就像下面这个例子一样,我们控制它的速度为"4s"一次打印:

public class SleepMessages {public static void main(String args[]) throws InterruptedException {String importantInfo[] = {"Mares eat oats","Does eat oats","Little lambs eat ivy","A kid will eat ivy too"};for (int i = 0;i < importantInfo.length;i++) {//Pause for 4 secondsThread.sleep(4000);//Print a messageSystem.out.println(importantInfo[i]);}}
}

sleep提供两个重载方法。一个指定毫秒,一个指定纳秒(nanosecond)。然而,正如我们在 The Java™ Tutorials——(3)Essential Classes——The Platform Environment 所讨论过的——这里所传入的毫秒,纳秒并不一定是绝对准确的,因为它们是由底层操作系统提供的基础设施所限制着。

​ 另外,Sleep时期能够被中断(interrupts)终止,interrupts我们将在后面深入讨论。

在任何情况下,我们都不能认为调用sleep就一定能够暂停线程指定的时间。

​ 再看到上面主函数抛出的异常throws InterruptedException。当线程sleep时,另一个线程中断(interrupts)当前线程,sleep就会抛出此异常。

2.3 中断(Interrupts)

​ 中断就是指示一个线程它应该停止它正在做的事情,转而做其它的事情。一个线程如何回应中断,完全取决于程序员,通常情况的做法是结束线程。

​ 线程通过在Thread对象上调用 interrupt 来发送一个中断使其被中断。为了使中断机制正常工作,中断的线程必须支持它自己的中断。

2.3.1 支持中断

​ 一个线程是怎样支持它自己的中断?这个取决于线程正在做什么。如果线程在频繁调用能够抛出InterruptedException的方法,那么就直接捕获异常然后从run方法返回。举个例子,将上面的SleepMessages修改一下让它支持中断。

for (int i = 0; i < importantInfo.length; i++) {// Pause for 4 secondstry {Thread.sleep(4000);} catch (InterruptedException e) {// We've been interrupted: no more messages.return;}// Print a messageSystem.out.println(importantInfo[i]);
}

​ 许多能够抛出InterruptedException的方法,如sleep,被设计来取消它们的当前操作并且当收到中断时,立刻返回。

​ 如果线程运行很长时间,并不调用能抛出InterruptedException异常的方法。那么它必须定期的调用Thread.interrupted,返回true表示中断接收到了。如:

for (int i = 0; i < inputs.length; i++) {heavyCrunch(inputs[i]);if (Thread.interrupted()) {// We've been interrupted: no more crunching.return;}
}

​ 上面代码中,简单测试中断,并且在中断时退出线程。在更复杂的应用中,可能抛出InterruptedException异常更加有意义。

if (Thread.interrupted()) {throw new InterruptedException();
}

​ 这能让中断处理代码集中在catch块中。

2.3.2 中断状态标识

​ 中断状态标识,(The Interrupt Status Flag)。

​ 通过使用一个叫做interrupt status的内部标识来实现中断机制。调用Thread.interrupt会设置此flag。当线程通过调用 Thread.interrupted检查中断时,中断状态会被清除。非静态方法isInterrupted 被一个线程用来来查询另一个线程的中断状态,并不会改变中断状态标识。

​ 习惯上,抛出InterruptedException 清除中断状态,任何方法都会退出。然而,中断状态可能会立即被再次设置——另一个线程调用interrupt

2.4 Joins

join 方法让一个线程等待另一个线程的完成。如果t是当前正在运行的线程,那么

t.join();

会导致当前线程停止执行,直到t的线程结束运行。join 的重载方法允许程序员指定等待的时长。

​ 与sleep类似,join 遇到InterruptedException异常,它会结束运行。

2.5 Thread程序例子

​ 下面给出一个例子,综合了前面所提到的一些概念。

​ 主线程从Runnable 对象MessageLoop创建一个新线程,并等待它完成。如果MessageLoop 线程花费时间太长,主线程就中断它。

public class SimpleThreads {// Display a message, preceded by// the name of the current threadstatic void threadMessage(String message) {String threadName =Thread.currentThread().getName();System.out.format("%s: %s%n",threadName,message);}private static class MessageLoopimplements 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 secondsThread.sleep(4000);// Print a messagethreadMessage(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 exitswhile (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 indefinitelyt.join();}}threadMessage("Finally!");}
}

3. 同步(Synchronization)

​ 线程通信主要是通过共享字段和引用字段引用的对象。这种通信是非常有效的,但是会造成两种可能的错误:线程冲突(Thread Interference)内存一致性错误(*thread interference* and *memory consistency errors*) 。防止这些错误的手段我们称之为 同步(*synchronization*)

​ 然而,同步会引入线程竞争(*thread contention*) 。线程竞争会发生在当两个或以上的线程同时尝试访问相同的资源,这导致java运行时执行一个或者多个线程更慢,或者甚至会暂停(suspend)它们的执行。饥饿和活锁(Starvation and livelock )就是线程竞争的例子。

3.1 线程冲突(Thread Interference)

​ 我们来看看一个计数器类:

class Counter {private int c = 0;public void increment() {c++;}public void decrement() {c--;}public int value() {return c;}
}

​ 如果一个Counter对象被多个线程引用。线程间的干扰可能会导致 Counter对象的操作结果不符合我们的预期。

​ 交错,interleave。当两个操作运行在不同的线程,但是作用在相同的数据上,干扰可能会发生。这意味着两个操作由多个步骤组成,操作步骤的顺序可能会"同时发生"(overlap

​ 看上去,好像Counter实例的操作不会交错,因为它们都只有简单的一条执行语句。然而,再简单的语句,它都能被JVM拆分成多个执行步骤。我们不必细究JVM具体采取的步骤,知道表达式 c++能够被分成如下3个步骤就够了:

  1. 取出c的当前值
  2. 将取出来的c值+1
  3. 将+1后的结果存回c

表达式c--也用同样的方式。

​ 现在,假定线程A调用increment ,与此同时,线程B调用decrementc初始值为0。它们的交错执行顺序可能是下面的情况:

  1. Thread A: Retrieve c.线程A取c
  2. Thread B: Retrieve c.线程B取c
  3. Thread A: Increment retrieved value; result is 1.线程A +1
  4. Thread B: Decrement retrieved value; result is -1.线程B +1
  5. Thread A: Store result in c; c is now 1.线程A存回数据
  6. Thread B: Store result in c; c is now -1线程B存回数据

​ 结果就是线程A的操作结果丢失了——由线程B覆盖重写了。当然,这种交错执行顺序只是一种可能。线程B的操作结果丢失也是有可能的;或者就按照我们预期的执行结果没有任何错误。由于这种执行顺序是不可预知的(unpredictable),所以线程引用bugs很难被察觉/修复。

3.2 内存一致性错误(Memory Consistency Errors)

​ 内存一致性错误,当不同的线程对相同的数据有着不一致的视图(inconsistent views)。内存一致性错误的原因非常复杂,超出了本文的讨论范围。幸运地是,程序员不需要对这些原因有着细致地理解。程序员需要做的事情就是学会一些避免内存一致性错误策略。

​ 避免内存一致性错误的关键就是理解*happens-before* 关系。happens-before简单讲,就是要保证一个特定语句对内存的写操作要对另一个特定语句可见。怎么理解这句话?我们看个例子:

// int字段被定义并被初始化
int counter = 0;// counter字段被线程A,B共享// 线程A操作:
counter++;// 线程B立刻执行:
System.out.println(counter);

​ 如果两条语句在同一线程执行,那么毫无疑问可以得到打印结果为:1。然而,这两条语句在不同的两个线程执行,被打印的值可能为:0。因为我们并不能保证线程A对counter的改变立即对线程B可见——除非程序员在这两条语句之间建立happens-before关系。

​ 有很多方法来创建happens-before关系,其中一个就是同步(synchronization),接下来的内容会陆续介绍。

​ 其实我们已经见识过了两种创建happens-before relationships的行为了:

  • When a statement invokes Thread.start, every statement that has a happens-before relationship with that statement also has a happens-before relationship with every statement executed by the new thread.(太难翻译了吧。that…that…all that???)The effects of the code that led up to the creation of the new thread are visible to the new thread.

    当一个语句调用thread.start时,与该语句有happens-before关系的每个语句也与新线程执行的每个语句有before关系。新线程可以看到导致创建新线程的代码的效果。

  • 当一个线程结束,导致另一个线程的Thread.join返回,已经结束的线程已经执行了的所有语句和成功join后的语句有着happens-before relationship。执行join的线程现在可以看到终止了的线程执行代码后的效果。

​ 更多创建happens-before关系的行为,可以参考 Summary page of the java.util.concurrent package

3.3 Synchronized方法

synchronized methods and synchronized statements(同步方法,同步语句)是Java语言提供的两种基本同步基本方法。同步语句更加复杂,我们在后面讨论,现在我们讨论同步方法,给个例子:

public class SynchronizedCounter {private int c = 0;public synchronized void increment() {c++;}public synchronized void decrement() {c--;}public synchronized int value() {return c;}
}

​ 让这些方法用关键字synchronized修饰使其成为同步方法由两个效果:

  • 同一对象上的同步方法的两次调用是不可能交错执行的。当一个线程执行一个对象上的同步方法时,所有调用此对象上同步方法的其它线程都会阻塞{block or say (suspend execution) },直到最初的那个线程执行完。

  • 当一个同步方法结束的时候,它会自动地与随后任意的此对象上的同步方法调用形成happens-before关系。这保证了对象的状态对所有线程可见。

Warning: 当构造一个会在多个线程间共享的对象时,要小心对象的引用不要过早的"泄露"。举个例子,假设你要维护一个名为instanceList,它包含每个类的实例。你可能会冒险添加下面的代码到你的构造器里面。但是,其它线程可能在你的对象被完全构造完成之前使用instances 访问此对象。

instances.add(this);

Synchronized方法是一种防止线程冲突(Thread Interference)和内存一致性错误的简单策略——如果一个对象对多个线程可见,所有对象上的读写操作都通过synchronized 方法。这种策略时很有效的,但是会呈现出活跃性问题,有关活跃性问题,我们之后会详细讨论。

​ 当然,对于final字段来说,它在对象被构造之后就不能被修改,所以对象一旦被构造完成,它就可以通过non-synchronized(非同步)方法来安全读取对象的状态。

3.4 内在锁与同步(Intrinsic Locks and Synchronization)

​ 同步是围绕称为内部锁或监视器锁的内部实体构建的。(The API specification 通常称之为监视器"monitor")。内部锁在同步方面扮演者一个非常重要的角色——强制对象状态的排他访问,并建立对可见性来说非常必要的happens-before关系。

​ 每个对象都有一个与之关联的内在锁。习惯上,一个需要排他且持续地访问一个对象字段的线程在访问对象之前应该要获取(acquire )此对象的内在锁,并且访问完此对象后释放此锁。一个线程在获取锁并释放锁的这段时间,我们称此线程拥有内在锁。只要一个线程拥有一个内在锁,其它线程就不能够获取此锁。如果另一个线程请求此锁,那么它会被阻塞(block )。

​ 当线程释放了内在锁,那么此行为与任意接下来的任意此锁的请求会建立一个 happens-before关系。

3.4.1 同步方法的锁

​ 当一个线程调用一个同步方法时,它会自动获取方法所属对象的锁,并在方法结束时释放此锁。(由异常导致的方法返回也算)

​ 当一个静态同步方法被调用时,由于静态方法是跟类关联而非单个对象;在这种情况下,线程会获取类关联的Class 对象的内在锁。因此控制类静态字段访问的锁与类的实例的锁不同。

3.4.2 同步语句

​ 同步语句必须要指定提供内在锁的对象。

public void addName(String name) {synchronized(this) {lastName = name;nameCount++;}nameList.add(name);
}

​ 不能同时递增的两个变量。

public class MsLunch {private long c1 = 0;private long c2 = 0;private Object lock1 = new Object();private Object lock2 = new Object();public void inc1() {synchronized(lock1) {c1++;}}public void inc2() {synchronized(lock2) {c2++;}}
}

3.4.3 可重入的同步

​ 我们已经知道,一个线程不能够获取另一个线程已经拥有的锁。但是一个线程能够获取它自己已经拥有的锁。允许线程获取同样的锁不止一次,我们称之为可重入锁。同步代码调用一个方法也包含同步代码,一块代码中多次获取同一锁。如果没有可重入锁,同步代码就不得不花很多精力来防止线程将自己阻塞。

3.5 原子访问

​ 在编程中,原子操作是立刻有效地发生的。原子操作并不能在中途停止:要么完全执行完成,要么不执行。原子操作的中间结果是不可见的,知道操作完成。

​ 我们通过前面的学习,已经知道,表达式c++并不是原子操作。甚至非常复杂的表达式也能由许多复杂的其它小操作所组成。然而你需要知道一些操作是原子性的:

  • 对引用变量和大多数基本数据类型的变量(8种基本数据类型,除了longdouble)的读写是原子性的。
  • 所有用volatile修饰的变量的读写都是原子性的。(包括longdouble)。

​ 原子操作并不会被交错执行,所以他们不用担心thread interference的问题。然而这并不会消除所有需要同步的原子操作,因为内存一致性问题仍然可能发生。volatile 变量能够减少内存一致性问题的风险,因为所有的写操作都会与后续的读操作建立happens-before关系。 意思就是说,volatile变量的改变总是对其它线程可见的。更近一步, 当一个线程读取volatile变量的时候,该线程不但读取了最近的变化,而且是导致该变化发生代码的全部影响。

​ 使用原子变量来访问比使用同步代码块来访问更高效。但是程序员要更加关心避免内存一致性问题的发生,是否这种额外的关心有意义那得看程序的大小和复杂度。

java.util.concurrent包种提供了并不依赖同步的原子方法。我们将在后面的高级并发对象进一步章节讨论。

4. 活跃性

​ 应用及时执行的能力我们称之为活跃性(liveness)(A concurrent application’s ability to execute in a timely manner is known as its liveness)。

​ 这里我们将讨论一般的活跃性问题: 死锁。简要的介绍其它两个活跃性问题:饥饿活锁

4.1 死锁

Deadlock ,死锁,两个或者多个线程永远阻塞,互相等待。下面给出一个例子:

​ 这个例子是一个鞠躬的例子,我们要求严格遵守这种礼貌——当你向你的朋友鞠躬的时候,你必须要保持鞠躬的姿势,知道你的朋友向你回一个鞠躬礼。

​ 不幸的是,此规则不适用于当两个朋友同时向对方鞠躬。也就是说两个朋友同时向对方鞠躬,都等着对方回一个鞠躬礼。而这种等待会无线地持续下去。

public class Deadlock {static class Friend {private final String name;public Friend(String name) {this.name = name;}public String getName() {return this.name;}public synchronized void bow(Friend bower) {System.out.format("%s: %s"+ "  has bowed to me!%n", this.name, bower.getName());bower.bowBack(this);// 等待着对方给我回一个鞠躬}public synchronized void bowBack(Friend bower) {System.out.format("%s: %s"+ " has bowed back to me!%n",this.name, bower.getName());}}public static void main(String[] args) {final Friend alphonse = new Friend("Alphonse");final Friend gaston = new Friend("Gaston");new Thread( () -> alphonse.bow(gaston) ).start();new Thread( () -> gaston.bow(alphonse) ).start();}
}

4.2 饥饿与活锁

​ 相比死锁,饥饿与活锁问题就不那么常见了。但是每个并发软件的设计者仍然会碰到这种问题。

​ 饥饿(Starvation)。线程并不能获得共享对象的常规访问,并且迟迟得不到进展。这种情况可能发生在一个"贪心"的线程长期占据共享资源使其不可用。举个例子,假设一个同步方法常常会执行很长时间才返回。如果一个线程频繁调用此方法,其它也需要频繁同步访问此对象的线程将时常会被阻塞。

​ 活锁(livelock )。先举个例子再进行专业的解释:两个人在走廊上互相迎面走近,此时它们站在走廊的同一边,都想避开对方走过去,但是它们几乎是同时地避开对方走到另一边。可想而知,它们按照这种方式避开对方走下去,永远也不能避开对方通过走廊。这是现实生活中非常常见的例子。在现实中我们可能会来来回回避个一两次,直到有一个人停住不动,这种"活锁"才解决。然而计算机中,计算机没有意识,它不知道自己出现了这种情况,他不会像人一样意识到自己"活锁"了,计算机认为这是正常的响应。

​ 线程A常常会根据线程B的行为而做出响应行为,而线程B可能也会根据线程A的(响应)行为而做出响应行为,那么 "livelock" 活锁就可能发生。与死锁一样,活锁也不能取得任何实质性的进展。然而,不同的是,处于活锁状态的线程并不会阻塞,它只是持续地执行,但是执行没有任何实际上地意义——仅仅是频繁地互相回应。

5. 警戒块(Guarded Blocks)

​ 多个线程常常不得不协调它们的行为。最常见的协调方式就是guarded block。何为guarded block

​ 我们看下面的代码,while(!joy) {}就是一个guarded block

public void guardedJoy() {// Simple loop guard. Wastes// processor time. Don't do this!while(!joy) {}System.out.println("Joy has been achieved!");
}

​ 警戒块,通常是轮询一个状态,在此块能够被继续往后执行之前,此状态通常为true

​ 上面的代码我们不推荐,因为当它在"等待时",仍然在继续运行。一个更有效的方法就是调用object.wait来挂起当前线程。wait的调用不会返回,直到另一个线程发起一个notification;此notification可能是一些事件,这些事件并不一定是此线程正在等的。(The invocation of wait does not return until another thread has issued a notification that some special event may have occurred — though not necessarily the event this thread is waiting for)

public synchronized void guardedJoy() {// This guard only loops once for each special event, which may not// be the event we're waiting for.while(!joy) {try {wait();} catch (InterruptedException e) {}}System.out.println("Joy and efficiency have been achieved!");
}

注意!一定要在循环内部测试正被等待的状态。不要认为中断是你等待的某种特殊状态。

​ 和所有能够挂起执行的方法一样,wait能跑出InterruptedException异常。在上面的例子中,我们的异常并不关心joy的值,不做处理。

​ 为什么上面guardedJoy 的代码是同步的呢? 假设d是我们要调用wait的对象,当线程调用d.wait时,它必须拥有d的内在锁,否则就会抛出错误。synchronized方法中就是简单的获取内在锁的方法。

​ 当wait被调用时,线程会释放锁并挂起其执行。在将来的某个时刻,另一个线程可能会获取此锁并调用 Object.notifyAll来唤醒所有在此锁上wait的线程。

public synchronized notifyJoy() {joy = true;// 先置为truenotifyAll();// 然后唤醒wait中的线程
}

在第二个线程释放锁时,第一个线程在wait返回时会重新获取锁并继续执行。

​ 有另外一个唤醒函数:notify,它会唤醒单个的线程。由于notify 并不允许我们指定被唤醒的线程,所以它只在大量并行应用程序中有用;大量并行应用程序,指的是大量的线程做着类似的事情,你并不关心哪个线程被唤醒。

 让我们使用`guarded blocks`来创建一个*Producer-Consumer application*(生产者-消费者应用)。此应用在两个线程之间共享数据:*producer*创建数据,*consumer*处理被创建的数据。这两个线程使用共享对象来进行通信。它们之间的协作是很有必要的—— *consumer*  只在 *producer* 生产出数据之后在取用处理。*producer* 在 *consumer* 取出旧数据之后才投递新数据。

​ 下面是程序例子,注意各语句之间的顺序:

public class Drop {// Message sent from producer// to consumer.private String message;// True if consumer should wait// for producer to send message,// false if producer should wait for// consumer to retrieve message.private boolean empty = true;public synchronized String take() {// Wait until message is// available.while (empty) {try {wait();} catch (InterruptedException e) {}}// Toggle status.empty = true;// Notify producer that// status has changed.notifyAll(); return message;  // 置空->唤醒->返回/处理数据}public synchronized void put(String message) {// Wait until message has// been retrieved.while (!empty) {try { wait();} catch (InterruptedException e) {}}// Toggle status.empty = false;// Store message.this.message = message;// Notify consumer that status// has changed.notifyAll(); // 置非空->投递数据->唤醒}
}

​ 下面我们看生产者Producer的实现

public class Producer implements Runnable {private Drop drop;public Producer(Drop drop) {this.drop = drop;}public void run() {String importantInfo[] = {"Mares eat oats","Does eat oats","Little lambs eat ivy","A kid will eat ivy too"};Random random = new Random();for (int i = 0; i < importantInfo.length; i++) {drop.put(importantInfo[i]);try {Thread.sleep(random.nextInt(5000));} catch (InterruptedException e) {}}drop.put("DONE");}
}

​ 下面我们看看消费者Consumer的实现:

public class Consumer implements Runnable {private Drop drop;public Consumer(Drop drop) {this.drop = drop;}public void run() {Random random = new Random();for (String message=drop.take();!message.equals("DONE");message=drop.take()) {System.out.format("MESSAGE RECEIVED: %s%n", message);try {Thread.sleep(random.nextInt(5000));} catch (InterruptedException e) {}}}
}

​ 测试测试程序:

public class ProducerConsumerExample {public static void main(String[] args) {Drop drop = new Drop();(new Thread(new Producer(drop))).start();(new Thread(new Consumer(drop))).start();}
}

6. 不可变对象(Immutable Objects)

Immutable Objects,不可变对象。当一个对象被构造完成之后它的状态不能再改变,我们就认为它是一个Immutable Objects

Immutable Objects在并发程序中是很有用的。由于它们是不可变的,所以它不会因为线程冲突( thread interference)和不一致的状态被看到而被破坏。

​ 程序员通常不愿意采用Immutable Objects,因为它们担心创建新对象而不是更新对象状态所带来的性能开销。创建新对象的影响通常被高估了(overestimated),并且能够被Immutable Objects的一些性能效率所抵消。包括GC的开销减少,可变对象的可变性代码被移除。

​ 下面我们将介绍一个可变对象(mutable Object),然后逐渐将其演化成一个Immutable Object。我们将给出转换的一般策略,并且证明Immutable Object的一些优点。

6.1 同步类的例子

​ 下面给出一个可变对象类:SynchronizedRGB

public class SynchronizedRGB {// Values must be between 0 and 255. [0, 2^8 - 1]private int red;private int green;private int blue;private String name;private void check(int red, int green, int blue) {if (red < 0 || red > 255 ||green < 0 || green > 255 ||blue < 0 || blue > 255) {throw new IllegalArgumentException();}}public SynchronizedRGB(int red, int green, int blue, String name) {check(red, green, blue);this.red = red;this.green = green;this.blue = blue;this.name = name;}public void set(int red, int green, int blue, String name) {check(red, green, blue);synchronized (this) {this.red = red;this.green = green;this.blue = blue;this.name = name;}}public synchronized int getRGB() {return ((red << 16) | (green << 8) | blue);}public synchronized String getName() {return name;}public synchronized void invert() {red = 255 - red;green = 255 - green;blue = 255 - blue;name = "Inverse of " + name;}
}

​ 上面代码很容易理解,我们再看一段测试代码:

SynchronizedRGB color =new SynchronizedRGB(0, 0, 0, "Pitch Black");
...
int myColorInt = color.getRGB();      //Statement 1
String myColorName = color.getName(); //Statement 2

​ 上面代码所在线程的语句1和语句2之间,假设有另一个线程在此处调用color.set方法,那么,myColorIntmyColorName肯定是不匹配的,也就是说颜色数值与颜色名并不匹配。原因就是线程冲突(Thread Interference)引起的错误。

​ 怎么解决此问题?看下面代码:

// 获取color对象的内在锁
// 使语句1,2整体相对color对象来说为一个原子操作
synchronized (color) {int myColorInt = color.getRGB();// Statement 1String myColorName = color.getName();// Statement 2
}

​ 这种内存不一致性问题只会发生在mutable objects可变对象上,不可变对象版本的SynchronizedRGB这将不是一个问题。

6.2 设计Immutable对象的策略

​ 下面给出构造immutable objects的简单策略。并不是所有标识为immutable的类都遵循下列规则;这并不意味着类的设计者粗心大意——它们很可能有充足的理由认为它们设计的类在构造之后就一定不会改变。然而这些策略要求复杂的分析,并不适用于初学者。

  1. 不要提供"setter"方法。这里的setter表示可修改对象字段/引用字段的方法(对应于SynchronizedRGB例子中的setinvert方法)
  2. 所有字段声明为private final
  3. 不允许子类重写方法。最简单的方法就是将类声明为final。稍微复杂的方法就是将构造器私有化private,向外提供工厂方法来构造实例。
  4. 如果实例字段包含可变对象的引用,那么不允许这些对象被改变。
    • 不提供修改可变对象的方法
    • 不共享可变对象的引用。不要向外泄露对象的引用。必要时我们可以创建可变对象的拷贝,避免泄露内部原生的可变对象。

​ 我们现在根据上面的策略来修改SynchronizedRGB 类,使其成为一个不可变类ImmutableRGB

final public class ImmutableRGB {// Values must be between 0 and 255.final private int red;final private int green;final private int blue;final private String name;private void check(int red, int green, int blue) {if (red < 0 || red > 255 || green < 0 || green > 255 ||blue < 0 || blue > 255) {throw new IllegalArgumentException();}}public ImmutableRGB(int red,int green,int blue,String name) {check(red, green, blue);this.red = red;this.green = green;this.blue = blue;this.name = name;}public int getRGB() {return ((red << 16) | (green << 8) | blue);}public String getName() {return name;}public ImmutableRGB invert() {// return a copy of {this}return new ImmutableRGB(255 - red,255 - green,255 - blue,"Inverse of " + name);}
}
  • 删除set方法,invert方法则返回对象的拷贝。
  • 所有字段private final
  • 类声明为final类。finla public class ImmutableRGB { /*...*/ }
  • 只有一个字段是引用类型String,它本身就是Immutable的,我们不作处理。

7. 高级并发对象(High Level Concurrency Objects)

​ 到目前为止,本文已经研究了一番Java平台很早就开始支持的一些低级API。这些API足够完成一些基本的任务,但是高级的任务需要更加高级的代码来完成。特别是对于大规模应用程序来说,它能充分发掘现今多处理器和多核计算机的潜力。

​ 这节我们来研究研究Java 5引入的高级并发特性。大多数的特性实现都在java.util.concurrent 包下,也有一些并发数据结构在Java集合框架中( Java Collections Framework

  • Lock objects。锁对象, 支持锁的一般用法。
  • Executors。定义启动和管理线程的高级API。java.util.concurrent 下Executor实现提供了线程池管理 ,适用于大规模应用程序。
  • Concurrent collections。更易管理大量的数据集合,在外部极大降低了同步需求。
  • Atomic variables。最小化同步操作,帮助避免内存一致性错误。
  • ThreadLocalRandom (in JDK 7)。提供多线程环境中有效率的伪随机数的生成。

7.1 锁对象(Lock Objects)

​ 同步代码依赖简单的可重入锁(reentrant lock)。这种锁易用,但是有许多限制。更加复杂的锁用法在java.util.concurrent.locks 包下都有支持。但是我们并不会详细地探究此包,而是将注意力集中到这个最基本的接口—— Lock

package java.util.concurrent.locks;import java.util.concurrent.TimeUnit;public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();
}

Lock 对象工作机制非常像synchronized代码使用的隐式锁。和隐式锁一样,任何时刻只能有一个线程拥有Lock 对象。Lock 对象通过与之关联的 Condition 对象来支持wait/notify 机制,

​ Lock对象在隐式锁上最大的优点就是它能够收回"获取锁的尝试"(back out of an attempt to acquire a lock)。 如果锁不可用或者在一定的时间内(如果指定了)不可用,那么tryLock 方法会立即退出(back out);如果另一个线程在锁被获取之前发送了一个中断,lockInterruptibly方法会退出(back out)。

​ 下面我们使用Lock 对象来解决我们之前在4.活跃性部分提到的死锁例子。

​ 在继续鞠躬之前必须先获取两个参与者的锁。

public class Safelock {static class Friend {private final String name;private final Lock lock = new ReentrantLock();public Friend(String name) {this.name = name;}public String getName() {return this.name;}public boolean impendingBow(Friend bower) {Boolean myLock = false;Boolean yourLock = false;try {myLock = lock.tryLock();yourLock = bower.lock.tryLock();} finally {if (! (myLock && yourLock)) {if (myLock) {lock.unlock();}if (yourLock) {bower.lock.unlock();}}}return myLock && yourLock;}public void bow(Friend bower) {if (impendingBow(bower)) {try {System.out.format("%s: %s has"+ " bowed to me!%n", this.name, bower.getName());bower.bowBack(this);} finally {lock.unlock();bower.lock.unlock();}} else {System.out.format("%s: %s started"+ " to bow to me, but saw that"+ " I was already bowing to"+ " him.%n",this.name, bower.getName());}}public void bowBack(Friend bower) {System.out.format("%s: %s has" +" bowed back to me!%n",this.name, bower.getName());}}static class BowLoop implements Runnable {private Friend bower;private Friend bowee;public BowLoop(Friend bower, Friend bowee) {this.bower = bower;this.bowee = bowee;}public void run() {Random random = new Random();for (;;) {try {Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {}bowee.bow(bower);}}}public static void main(String[] args) {final Friend alphonse =new Friend("Alphonse");final Friend gaston =new Friend("Gaston");new Thread(new BowLoop(alphonse, gaston)).start();new Thread(new BowLoop(gaston, alphonse)).start();}
}

7.2 Executors

​ 在大规模的应用中,从应用中分离出线程的管理和创建是很有意义的。封装这些函数的对象,我们称之为executors。接下来我们将详细介绍executors

  • Executor Interfaces。定义了3种executor对象类型。
  • Thread Pools。是最常见的executor实现are the most common kind of executor implementation.
  • Fork/Join(JDK 7)是一个充分利用多处理器的框架。

7.2.1 Executor 接口

java.util.concurrent定义3个Executor 接口:Executor,ExecutorService,ScheduledExecutorService

  • Executor。支持启动新任务的简单接口。
  • ExecutorService。Executor的子接口,添加了帮助管理单个任务和executor本身的特性。
  • ScheduledExecutorService。ExecutorService的子接口,支持将来或者周期性的任务执行。
package java.util.concurrent;public interface ScheduledExecutorService extends ExecutorService {public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
}
7.2.1.1 Executor
package java.util.concurrent;public interface Executor {void execute(Runnable command);
}

​ 下面两段代码是等效的:

// Runnable r = ...// Thread
(new Thread(r)).start();// Executor
e.execute(r);

​ 然而上面的execute的定义并不是那么明确,实际上,它底层的实现就是创建一个新线程然后立即启动。execute 可能会做相同的事情,但是我们更可能会使用一个工作线程来运行r,或者将r放在队列中等待工作线程变得可用。

​ java.util.concurrent包中的executor实现被设计来充分利用更加高级的ExecutorServiceScheduledExecutorService 接口。

7.2.1.2 ExecutorService
public interface ExecutorService extends Executor {void shutdown();List<Runnable> shutdownNow();boolean isShutdown();boolean isTerminated();boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;<T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);Future<?> submit(Runnable task);<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException;<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException;<T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException;<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

​ ExecutorService提供了一种类似的,但是更加通用的submit 方法。submit 既接收Runnable 对象传参,也接收 Callable 对象传参,callable允许任务有返回值。submit 方法返回一个 Future 对象,Future被用来取出Callable 的返回值,并管理CallableRunnable 任务的状态。

ExecutorService 也提供方法支持提交大量的Callable 对象的集合。它也提供方法来管理executor的关闭。为了支持迅速关闭,任务应该正确地处理中断。

@FunctionalInterface
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}
public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
7.2.1.3 ScheduledExecutorService

ScheduledExecutorService提供带有"周期执行"功能的 ExecutorService 。它会在一个指定的延时之后指定Runnable 或者Callable 任务。除此之外,接口还定义scheduleAtFixedRatescheduleWithFixedDelay,它们能够在指定时间间隔重复指定任务。

7.3 Thread Pools

java.util.concurrent 包下大多数executor实现使用线程池( thread pools),线程池由工作线程组成。这种线程与Runnable 或者Callable任务分开存在,通常用于执行多个任务。

​ 使用工作线程最小化线程创建的开销。线程对象使用一个一定数量的内存,并且在大规模应用中,分配与释放线程对象会造成一定的内存管理开销。

​ 一种常见的线程池类型叫做 fixed thread pool,固定线程池。这种线程池总是有一定数量的线程在运行。如果一个线程在运行过程中终止了,它会自动被新线程替换。 通过一个内部队列来提交任务,此队列会占据额外的的任务,不管什么时候活跃的任务数都会比线程数多。

​ fixed thread pool的一个重要的优点就是应用会使用它degrade gracefully。怎么理解degrade gracefully?一个HTTP Web服务器没接受一个请求就开启一个线程来处理它,那么如果短时间内大量的请求传入服务器,应用很可能会因为内存容量而宕机,造成服务器停止回应所有的请求。而如果被创建的线程数量有限制的话,服务器将不会响应HTTP请求不像它们来的那么快,但是系统会根据系统支撑度尽快响应请求。

​ 一个简单创建fixed thread pool的方法就是调用 java.util.concurrent.Executors 的工厂方法 newFixedThreadPool 。Executors类也提供了下列工厂方法:

  • newCachedThreadPool。创建一个带有可扩张的线程池的executor。此executor适用于启动许多短期任务的应用。
  • newSingleThreadExecutor 。创建一个每次只执行单个任务的executor。
  • 许多工厂方法都是上面executor的ScheduledExecutorService 版本。

​ 如果上面的工厂方法提供的executor不符合你的需求,那么就构造java.util.concurrent.ThreadPoolExecutor 或者 java.util.concurrent.ScheduledThreadPoolExecutor 的实例试试看。

7.4 Fork/Join

Fork/Join 框架是ExecutorService 的接口实现,它帮助你充分利用多处理器。它被设计用来处理那些能够被递归划分成更小块的工作。它的目标就是发挥所有可用处理器的力量来提升程序的性能。

 和所有的`ExecutorService`实现一样,`Fork/Join` 框架分配任务给线程池中的工作线程。不同之处在于它的*work-stealing* 算法。`工作线程完成任务后,可以窃取其它正忙的线程的任务。`

​ fork/join framework的核心就是ForkJoinPool 类。 ForkJoinPool 实现了work-stealing算法的核心,能够执行 ForkJoinTask 任务。

public class ForkJoinPool extends AbstractExecutorService {// ...
}
7.4.1 基本用法

​ 你的代码可能类似如下伪代码所示:

if (my portion of the work is small enough)do the work directly
elsesplit my work into two piecesinvoke the two pieces and wait for the results

​ 将上面的代码放在ForkJoinTask 子类里,典型的类型有: RecursiveTaskRecursiveAction,其中前者有返回值。

​ 在ForkJoinTask 子类准备好后,创建一个表示一个所有工作要被完成的对象,并将此对象传入ForkJoinPool 实例的 invoke() 方法。

7.4.2 图像模糊处理的例子

​ 为了理解fork/join framework,我们考虑一个将图像模糊的程序例子。假设我们需要模糊一个图像,源图像由一个整数数组表示,每单个数组值表示单个像素的颜色值。被模糊的目标图像也用同样大小的数组来存储。

​ 每次处理一个像素点,将其模糊化。每个像素被它周围的像素所平均化,并将得到的像素值写入目标图像数组。由于图像是一个比较大的数组,那么这样处理会花很长时间。我们通过使用fork/join framework实现算法能够充分利用多核计算机的并发处理优点。

​ 下面给出一个可能的代码实现:

public class ForkBlur extends RecursiveAction {private int[] mSource;private int mStart;private int mLength;private int[] mDestination;// Processing window size; should be odd.private int mBlurWidth = 15;public ForkBlur(int[] src, int start, int length, int[] dst) {mSource = src;mStart = start;mLength = length;mDestination = dst;}protected void computeDirectly() {int sidePixels = (mBlurWidth - 1) / 2;for (int index = mStart; index < mStart + mLength; index++) {// Calculate average.float rt = 0, gt = 0, bt = 0;for (int mi = -sidePixels; mi <= sidePixels; mi++) {int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1);int pixel = mSource[mindex];rt += (float)((pixel & 0x00ff0000) >> 16) / mBlurWidth;gt += (float)((pixel & 0x0000ff00) >>  8) / mBlurWidth;bt += (float)((pixel & 0x000000ff) >>  0) / mBlurWidth;}// Reassemble destination pixel.int dpixel = (0xff000000     ) |(((int)rt) << 16) |(((int)gt) <<  8) |(((int)bt) <<  0);mDestination[index] = dpixel;}}...

​ 现在实现抽象的compute类,是否分开执行,看数组的长度而定。

protected static int sThreshold = 100000;protected void compute() {if (mLength < sThreshold) {computeDirectly();return;}int split = mLength / 2;invokeAll(new ForkBlur(mSource, mStart, split, mDestination),new ForkBlur(mSource, mStart + split, mLength - split,mDestination));
}

​ 如果之前的方法是RecursiveAction 子类,那么将任务安排到ForkJoinPool 内执行,然后按照下列步骤调用:

// 1. Create a task that represents all of the work to be done.// source image pixels are in src
// destination image pixels are in dst
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);// 2. Create the ForkJoinPool that will run the task.
ForkJoinPool pool = new ForkJoinPool();// 3. Run the task.
pool.invoke(fb);

​ 下面我们看一下完整的代码吧:

public class ForkBlur extends RecursiveAction {private int[] mSource;private int mStart;private int mLength;private int[] mDestination;private int mBlurWidth = 15; // Processing window size, should be odd.public ForkBlur(int[] src, int start, int length, int[] dst) {mSource = src;mStart = start;mLength = length;mDestination = dst;}// Average pixels from source, write results into destination.protected void computeDirectly() {int sidePixels = (mBlurWidth - 1) / 2;for (int index = mStart; index < mStart + mLength; index++) {// Calculate average.float rt = 0, gt = 0, bt = 0;for (int mi = -sidePixels; mi <= sidePixels; mi++) {int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1);int pixel = mSource[mindex];rt += (float) ((pixel & 0x00ff0000) >> 16) / mBlurWidth;gt += (float) ((pixel & 0x0000ff00) >> 8) / mBlurWidth;bt += (float) ((pixel & 0x000000ff) >> 0) / mBlurWidth;}// Re-assemble destination pixel.int dpixel = (0xff000000)| (((int) rt) << 16)| (((int) gt) << 8)| (((int) bt) << 0);mDestination[index] = dpixel;}}protected static int sThreshold = 10000;@Overrideprotected void compute() {if (mLength < sThreshold) {computeDirectly();return;}int split = mLength / 2;invokeAll(new ForkBlur(mSource, mStart, split, mDestination),new ForkBlur(mSource, mStart + split, mLength - split, mDestination));}// Plumbing follows.public static void main(String[] args) throws Exception {String srcName = "red-tulips.jpg";File srcFile = new File(srcName);BufferedImage image = ImageIO.read(srcFile);System.out.println("Source image: " + srcName);BufferedImage blurredImage = blur(image);String dstName = "blurred-tulips.jpg";File dstFile = new File(dstName);ImageIO.write(blurredImage, "jpg", dstFile);System.out.println("Output image: " + dstName);}public static BufferedImage blur(BufferedImage srcImage) {int w = srcImage.getWidth();int h = srcImage.getHeight();int[] src = srcImage.getRGB(0, 0, w, h, null, 0, w);int[] dst = new int[src.length];System.out.println("Array size is " + src.length);System.out.println("Threshold is " + sThreshold);int processors = Runtime.getRuntime().availableProcessors();System.out.println(Integer.toString(processors) + " processor"+ (processors != 1 ? "s are " : " is ")+ "available");ForkBlur fb = new ForkBlur(src, 0, src.length, dst);ForkJoinPool pool = new ForkJoinPool();long startTime = System.currentTimeMillis();pool.invoke(fb);long endTime = System.currentTimeMillis();System.out.println("Image blur took " + (endTime - startTime) + " milliseconds.");BufferedImage dstImage =new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);dstImage.setRGB(0, 0, w, h, dst, 0, w);return dstImage;}
}
7.4.3 SE中的实现

java.util.ArraysparallelSort() 方法。(Java SE8)

java.util.streams的lambda部分 (Java SE 8)

7.3 Concurrent Collections

java.util.concurrent包包含大量Java集合框架的扩展。按集合接口如下分类:

  • BlockingQueue defines a first-in-first-out data structure that blocks or times out when you attempt to add to a full queue, or retrieve from an empty queue.(定义FIFO数据结构,当你尝试从空队列中取数据或者满队列中添加数据,会引起阻塞或者超时等待)

  • ConcurrentMap is a subinterface of java.util.Map that defines useful atomic operations. These operations remove or replace a key-value pair only if the key is present, or add a key-value pair only if the key is absent. Making these operations atomic helps avoid synchronization. The standard general-purpose implementation of ConcurrentMap isConcurrentHashMap, which is a concurrent analog of HashMap.(java.util.Map的子接口,定义了一些有用的原子操作,只有当key存在的时候才可替换或者移除key-value对,只有当key不存在的时候才可添加key-value对。此接口的一般实现为ConcurrentHashMap,HashMap的并发模拟)

  • ConcurrentNavigableMap is a subinterface of ConcurrentMap that supports approximate matches. The standard general-purpose implementation of ConcurrentNavigableMap isConcurrentSkipListMap, which is a concurrent analog of TreeMap.(ConcurrentNavigableMapConcurrentMap 的子接口,它支持近似匹配,一般的实现为ConcurrentSkipListMap,它是TreeMap的并发模拟 )

​ 以上的所有集合都帮助避免内存一致性问题——在添加对象到集合中的操作此操作的后续访问或者移除对象的操作之间建立happens-before关系。

7.4 Atomic Variables

java.util.concurrent.atomic 定义了支持单个变量上原子操作的类。所有类都有getset方法,类似于在volatile变量上进行读/写操作。也就是说,set和后续的任意get操作有着happens-before关系。compareAndSet 原子方法 和 整数原子变量上的简单原子算数方法 都有这种内存一致性特性。

​ 为了说明此包怎么用,我们回到3.1 线程引用章节讨论的Counter类:

class Counter {private int c = 0;public void increment() {c++;}public void decrement() {c--;}public int value() {return c;}
}

​ 一种避免线程干扰的方法就是使用同步方法,如下 SynchronizedCounter

class SynchronizedCounter {private int c = 0;public synchronized void increment() {c++;}public synchronized void decrement() {c--;}public synchronized int value() {return c;}
}

​ 对于这种简单的类,同步方法是可接受的。但是对于更复杂的类,此方法会造成活跃性的影响。我们可以将int基本数据类型替换成AtomicInteger 原子整数类型,这能防止线程竞争,且不对同步进行重排序。(prevent thread interference without resorting to synchronization):

import java.util.concurrent.atomic.AtomicInteger;class AtomicCounter {private AtomicInteger c = new AtomicInteger(0);public void increment() {c.incrementAndGet();}public void decrement() {c.decrementAndGet();}public int value() {return c.get();}}

7.5 Concurrent Random Numbers

​ Concurrent Random Numbers,并发随机数。JDK7中 java.util.concurrent 有一个很方便的类 ThreadLocalRandom ,应用能够用在多线程或者ForkJoinTasks中来产生随机数。

​ 在并发环境下,使用ThreadLocalRandom 而非Math.random()会导致更少的竞争从而有更好的性能。

​ 用法很简单,我们只需要调用ThreadLocalRandom.current(),然后链式调用方法取出一个随机数,如:

int r = ThreadLocalRandom.current().nextInt(4, 77);

8. 参考读物/工具

  • Concurrent Programming in Java: Design Principles and Pattern (2nd Edition) by Doug Lea.

    • 并发相关综合的书,作者是顶尖专家,他是Java平台并发框架的缔造者。
  • Java Concurrency in Practice by Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea.
    • 初学者就能上手的并发实践指导
  • Effective Java Programming Language Guide (2nd Edition) by Joshua Bloch.
    • 虽然它是一本Java通用编程指南,但是它的线程章节包含了并发编程的最佳实践
  • Concurrency: State Models & Java Programs (2nd Edition), by Jeff Magee and Jeff Kramer.
    • 通过模型和实际的例子,介绍并发编程
  • Java Concurrent Animated:并发特性的动画演示。

The Java™ Tutorials——(5)Essential Classes——Concurrency相关推荐

  1. Java虚拟机(JVM)参数配置说明

    http://lavasoft.blog.51cto.com/62575/25492/ Java虚拟机(JVM)参数配置说明   在Java.J2EE大型应用中,JVM非标准参数的配置直接关系到整个系 ...

  2. Ant—使用Ant构建一个简单的Java工程(两)

    博客<Ant-使用Ant构建一个简单的Java项目(一)>演示了使用Ant工具构建简单的Java项目,接着这个样例来进一步学习Ant: 上面样例须要运行多条ant命令才干运行Test类中的 ...

  3. 那些让面试官直呼内行的Java知识点(一)

    宝子们,你们要的面试题续集终于来啦~ 很多小伙伴反馈过,以前这篇 Java核心知识点精心整理 太长了,打开时页面都要卡一下 所以为了提升阅读体验,这次的面试题会分成若干小章节,每章只装十道题~ 开始吧 ...

  4. JAVA高级---(2)15分钟入门JVM底层原理

    建议阅读时长 15min 文章中知识点,有部分来源自java官网以及比较优秀的博主,以及深入理解java虚拟机的书和java虚拟机规范,在此表示感谢,对于其文章中不明确的知识点进行深入的分析和编写. ...

  5. Java选择题(七)

    1.在JAVA中, 下列标识符合法的是() 正确答案: C 你的答案: C (正确) A.3kyou B.@163 C.name D.while 解释: JAVA中,标识符, 指用于给变量.类.方法名 ...

  6. 【2022最新Java面试宝典】—— Java虚拟机(JVM)面试题(51道含答案)

    目录 一.Java内存模型 1. 我们开发人员编写的Java代码是怎么让电脑认识的 2. 为什么说java是跨平台语言 3. Jdk和Jre和JVM的区别 4. 说一下 JVM由那些部分组成,运行流程 ...

  7. java单元测试(一):MyBatis实现DAO-单元测试怎么写?

    java单元测试(一):MyBatis实现DAO层的单元测试怎么写? 简介 平时工作中,DAO始终包含了或多或少的逻辑,即使能够通过功能测试来达到测试DAO的目的,但通过单元测试来测试DAO的实现是更 ...

  8. JAVA 前世今生(三)

    JAVA 前世今生(三) Java语言最早被称为Oak,它是为了实现嵌入式的消费类电子产品应用而产生的,它的作者是James Gosling.Ed Frank, Patrick Naughton, J ...

  9. java入门(p1)进入java的世界

    浅谈java世界(连载中P1) Java是一门语言,它并不是很难理解的东西,语言是来进行交流的工具,那么它用来跟谁来交互呢,所有的语言都有与其交流的对象,中文也好英文也罢,交流基本的对象应该有人对吧, ...

最新文章

  1. js正则贪婪模式_JavaScript正则表达式迷你书之贪婪模式-学习笔记
  2. 首发:徐亦达团队新论文推荐(ECCV2020):端到端多人多视角3d动态匹配网络
  3. 大话设计模式—组合模式
  4. Java中内存中的Heap、Stack与程序运行的关系
  5. 虚拟Python环境可以这样创建
  6. 随想录(exe和dll的相互调用)
  7. 图解一致性哈希算法,看这文就够了!
  8. python输出csv中的双引号
  9. JAVA中几种循环结构的表示_本文通过实例讲解给大家介绍Java中for、while、do while三种循环语句的区别,具体详情如下所示:第一种:for循环 循环结构for语句的格式...
  10. 跳转html时请求头怎么取,如何获取a链接的请求头信息?
  11. 四种方法实现两个等大数组之间内容交换
  12. Python爬虫之抓取豆瓣影评数据
  13. html网站统计来访人数,实现网站访问人数统计
  14. win7连接惠普打印机p1108
  15. App加密那点事浅探爱加密原理
  16. 怎样远程控制另一台电脑
  17. 小程序开发API之改变置顶栏文字内容 wx.setTopBarText(已停,只有旧版微信有效)
  18. 第四次作业 ——吴靖瑜
  19. 那些百万年薪的算法工程师,都是经历了哪些九死一生?
  20. 【VBA研究】如何防止用户关闭窗体

热门文章

  1. linux如何卸载lightdm,告诉你Ubuntu安装LightDM的方法及命令
  2. Linux刻录系统文件ISO到光盘
  3. 浦江县教育计算机网上网认证系统,校园网使用FAQ
  4. 2020Android面试题跳楼大整理,京东-字节跳动面经+个人总结+心得
  5. PX4 FMU启动流程 2. 一、 nsh_newconsole
  6. Pixhawk---基于NSH的Firmware开发与调试
  7. ardupilot避障代码分析
  8. wow Warlock shushia PVP DZ
  9. 提高APP冷启动速度小结
  10. C语言教学模式 教学方法改革,C语言教学模式改革探究.docx