多线程和并行程序设计
多线程使得程序中的多个任务可以同时执行。
Java的重要功能之一就是内部支持多线程。多线程指在同一个程序中允许同时运行多个任务。在其它许多程序设计语言中,是通过调用依赖于系统的过程或者函数来实现多线程。
线程的概念
线程是指一个任务从头到尾的执行流程。一个程序可能包含多个可以同时运行的任务。
线程提供了运行一个任务的机制,对于 Java 而言,可以在一个程序中并发地启动多个线程。这些线程可以在多处理器系统上同时运行。在单处理器系统系统中,多个线程共享单个 CPU ,多个线程共享 CPU 时间称为时间分享,操作系统负责调度及分配资源给它们。注意,这里 CPU 大部分时间都是空闲的。
多线程可以使程序反应更快,交互性更强,执行效率更高。在一些情况下,即使在单处理器系上,多线程程序的运行速度也比单线程更快。Java 对多线程程序的创建和运行,以及锁定资源以避免冲突提供了非常好的支持。
可以在程序中创建附加的线程以执行并发任务。在 Java 中,每个任务都是 Runnable 接口的一个实例,也称为可运行对象(runnable object)。线程本质上讲就是便于任务执行的对象。
创建任务和线程
一个任务类必须实现 Runnable 接口。任务必须从线程运行。
任务就是对象。为了创建任务,必须首先为任务定义一个实现 Runnable 接口的类。Runnable 接口只包含一个 run 方法。需要实现这个方法告诉系统线程将如何运行。开发一个任务类的模板如下:
//Custom task class
public class TaskClass implements Runnable{
···
public TaskClass(...){
···
}
//Implement the run method in Runnable
public void run(){
//Tell system how to run custom thread
···
}
···
}
//Client class
public class Client{
···
public void someMethod(){
···
//Create an instance of TaskClass
TaskClass task = new TaskClass(...);
//Create a thread
Thread thread = new Thread(task);
//Start a thread
thread.start();
···
}
···
}
一旦定义一个 TaskClass ,就可以用它的构造方法创建一个任务。任务必须在线程中执行。Thread 类包含创建线程的构造方法以及控制线程的多种方法。创建一个任务后,可以创建该任务的线程,然后调用 start() 方法告诉 Java 虚拟机该线程准备运行。Java 虚拟机通过调用任务的 run() 方法执行任务。当 run() 方法执行完毕,线程就终止。
注意: 任务中的 run() 方法指明如何完成这个任务。Java 虚拟机会自动调用该方法,无需特意调用它。直接调用 run() 方法只是在同一个线程中执行该方法,而没有新方法被启动。
Thread 类
Thread 类包含为任务而创建的线程的构造方法,以及控制线程的方法。
public class Thread
extends Object
implements Runnable
Thread 类实现了 Runnable ,可以定义一个 Thread 的拓展类,并且实现 run 方法。然后,在客户端程序中创建这个类的一个对像,并且调用它的 start 方法来启动线程。但是,不推荐使用这种方法,这样设计会将任务和运行任务的机制混在一起,将任务从线程中分离出来是比较好的设计。
//Custom thread class
public class CustomThread extends Thread{
···
public CustomThread(...){
···
}
//Override the run method in Runnable
public void run(){
//Tell system how to perform this task
···
}
···
}
//Custom class
public class Client{
···
public void someMethod(){
···
//Create a thread
CustomThread thread1 = new CustomThread(...);
//Start a thread
thread1.start();
···
//Create another thread
CustomThread thread2 = new CustomThread(...);
//Start a thread
thread2.start();
···
}
···
}
Thread 类包含控制线程的方法:
| 方法 | 说明 |
|---|---|
| +Thread() | 创建一个空线程 |
| +Thread(task:Runnable) | 为指定的任务创建一个线程 |
| +start():void | 开始一个线程导致 JVM 调用 run() 方法 |
| +isAlive():boolean | 测试线程当前是否在运行 |
| +setPriority(p:int):void | 为该线程指定优先级 p(取值从 1 到 10) |
| +join():void | 等待该线程结束 |
| +sleep(millis:long):void | 让一个线程休眠指定时间,以毫秒为单位 |
| +yield():void | 引发一个线程暂停并允许其它线程执行 |
| +interrupt():void | 中断该线程 |
注意: Thread 类中还包含 stop() 、suspend() 、resume() 。这些方法具有内在的不安全因素,所以不提倡使用。为替代 stop() 方法,可以通过给 Thread 变量赋值 null 来表明它已经停止。
使用 yield() 方法为其它线程临时让出 CPU 时间。使用 join() 方法使一个线程等待另一个线程的结束。方法 sleep(long mills) 可以将该线程设置为休眠以确保其它线程的执行,休眠时间为指定的毫秒数。
sleep 方法可能抛出一个 InterruptedException ,这是一个必检异常。当一个休眠线程的 interrupt() 方法被调用时,就会产生该异常。不过,这个方法极少被调用,不太可能产生 InterruptedException 异常。
必检异常 InterruptedException 必须在 try-catch 块中处理,不能向上抛出。如果在循环中调用了 sleep 方法,就必须把整个循环放在 try-catch 块中,否则即使线程被中断,也可能会继续执行。
Java 给每个线程指定一个优先级。默认情况下,线程继承生成它的线程的优先级。可以用 setPriority 方法提高或者降低线程的优先级,使用 getPriority 方法获取线程的优先级。
优先级是从 1 到 10 的数字。Thread 类有 int 型常量 MIN_PRIORITY 、NORM_PRIORITY 、MAX_PRIORITY ,分别代表 1 、5 、10 。主线程优先级是 Thread.NORM_PRIORITY。
Java 虚拟机总是选择当前优先级最高的可运行线程。较低优先级的线程只有在没有比它更高的优先级的线程运行时才能运行。如果所有可运行线程具有相同的优先级,那将会用优先队列给它们分配相同的 CPU 份额。这称为循环调度(round-robin scheduling)。
提示: Java 不同的版本中的优先级的数字可能会改变。不过总可以使用 Thread 类中的常量来指定线程优先级。
提示: 如果总有一个优先级较高的线程在运行,或者有一个相同优先级的线程不退出,那么这个线程可能永远没有运行的机会。这种情况称为资源竞争或缺乏。为避免竞争现象,高优先级的线程必须定时地调用 sleep 方法或者 yield 方法,给低优先级或者相同优先级的线程一个运行的机会。
线程池
可以使用线程池来高效执行任务。
实现 java.lang.Thread 定义一个任务类,创建一个线程来运行一个任务。这种方法对于单一任务的执行很方便,缺点是必须为每隔任务创建一个线程,对于大量任务不够高效。为每个任务开始一个新线程可能会限制吞吐量并且造成性能降低。
线程池是管理并发执行任务个数的理想方法。Java 提供 Executor 接口来执行线程池中的任务,提供 ExecutorService 接口来管理和控制任务。ExecutorService 是 Executor 的子接口。
<<interface>>
java.util.concurrent.Executor
| 接口方法 | 说明 |
|---|---|
| +executor(object:Runnable):void | 执行可运行任务 |
Executor 接口执行线程,子接口 ExecutorService 管理线程:
<<interface>>
java.util.concurrent.ExecutorService
extends Executor
| 接口方法 | 说明 |
|---|---|
| +shutdown():void | 关闭执行器,但是允许执行器中的任务执行完。一旦关闭,则不再接受新的任务 |
| +shutdownNow():List | 立刻关闭执行器,即使池中还有未完成的线程。返回一个未完成任务的列表 |
| +isShutdown():boolean | 如果执行器已经关闭,则返回 true |
| +isTerminated():boolean | 如果池中的所有任务终止,则返回 true |
为了创建一个 Executor 对象,可以使用 Executor 类中的静态方法。Executor 类提供创建 Executor 对象的静态方法:
java.util.concurrent.Executors
extends Object
| 接口方法 | 说明 |
|---|---|
| +newFixedThreadPool(numberOfThreads:int):ExecutorService | 创建一个可以并行运行指定数目线程的线程池。一个线程在当前任务已经完成的情况下可以重用,来执行另外一个任务 |
| +newCachedThreadPool():ExecutorService | 创建一个线程池,它会在必要的时候创建新的线程,但是如果之前创建的线程可用,则先重用之前创建的线程 |
newFixedThreadPool(int) 方法在池中创建固定数目的线程。如果线程完成了任务的执行,它可以被重新使用以执行另外一个任务。如果线程池中所有的线程都不是处于空闲状态,而且有任务等待执行,那么在关闭之前,如果由于一个错误终止了一个线程,就会创建一个新线程来替代它。
如果线程池中所有的线程都不是处于空闲状态,而且有任务等待执行,那么 newCachedThreadPool() 方法就会创建一个新线程,所有的任务都并发地执行。如果缓冲池中的线程在 60 秒内都没有被使用就该终止它。对于小任务而言,一个缓冲池即足够。
方法 shutdown() 通知执行器关闭。不能接受新的任务,但是现有的任务将继续执行直至完成。
提示: 如果仅需要为一个任务创建一个线程,使用 Thread 类。若果需要为多个任务创建线程,最好使用线程池。
线程同步
线程同步用于协调相互依赖的线程的执行。
如果一个共享资源被多个线程同时访问,可能遭到破坏。会以一个引起冲突的方式访问一个公共资源。这是多线程程序中的一个普遍问题,称为竞争状态(race condition)。如果一个类的对象在多线程中没有导致竞争状态,则称这样的类为线程安全的(thread-safe)。
synchronized 关键字
为避免竞争状态,应该防止多个线程同时进入程序的某一特定部分,程序的这部分称为临界区(critical region)。可以使用关键字 synchronized 来同步方法,以便一次只有一个线程可以访问这个方法。
一个同步方法在执行前需要加锁。锁是一种实现资源排他使用的机制。
- 对于实例方法,要给调用该方法的对象加锁。
- 对于静态方法,要给这个类加锁。
如果一个线程调用一个对象上的同步实例方法(或同步静态方法),首先给对象(或类)加锁,然后执行该方法,最后解锁。在解锁之前,另一个调用那个对象(或类)中方法的线程将被阻塞,知道解锁。
同步语句
调用一个对象上的同步实例方法,需要给该对象加锁。调用一个类上的同步静态方法,需要给该类加锁。当执行方法中某一个代码块时,同步语句不仅可用于对 this 对象加锁,而且可用于对任何对象加锁。这个代码块称为同步块(synchronized block)。同步语句的一般形式如下所示:
synchronized (expr){
statements;
}
表达式 expr 求值结果必须是一个对象的引用。如果该对象已经被另一个线程锁定,则在解锁之前,该线程将被阻塞。当获准对一个对象加锁时,该线程执行同步块中的语句,然后解除给对象所加的锁。
同步语句允许设置同步方法中的部分代码,而不必是整个方法,这增强了程序的并发能力。
注意: 任何同步的实例方法都可以转换为同步语句。下面两个同步实例方法等价:
public synchronized void method(){
//method body
}
@等价于
public void method(){
synchronized(this){
//metnod body
}
}
利用加锁同步
可以显示地采用锁和状态来同步线程。
同步的实例方法在执行方法前都隐式地需要一个加在实例上的锁。
Java 可以显示地加锁,这给协调线程带来了更多的控制功能。一个锁是一个 lock 接口的实例,它定义了加锁和释放锁的方法。锁也可以使用 newCondition() 方法来创建任意个数的 Condition 对象,用来进行线程通信。
<<interface>>
java.util.concurrent.locks.Lock
| 接口方法 | 说明 |
|---|---|
| +lock():void | 得到一个锁 |
| +unlock():void | 释放锁 |
| +newCondition():Condition | 返回一个绑定到该 Lock 实例的 Condition 实例 |
ReentrantLock 是 Lock 的一个具体实现,用于创建相互排斥的锁。可以创建具有特定的公平策略的锁。公平策略为真,则确保等待时间最长的线程首先获得锁。取值为假的公平策略将锁给任意一个在等待的线程。被多个线程访问的使用公正锁的程序,其整体性能可能比使用默认设置的程序差,但是在获取锁且避免资源匮乏是可以有更小的时间变化。ReentrantLock 类实现接口 Lock 来表示一个锁:
java.util.concurrent.locks.ReentrantLock
extends Object
implements Lock,Serializable
| 方法 | 说明 |
|---|---|
| +ReentrantLock() | 等价于 ReentrantLock(false) |
| +ReentranLock(fair:boolean) | 根据给定的公平策略创建一个锁。如果 fair 为真,一个最长等待时间的线程将得到该锁。否则,没有特别的访问次序 |
提示: 在对 lock() 的调用之后紧随一个 try-catch 块 并且在 finally 子句中释放这个锁是一个很好的编程习惯。这样可以确保锁被释放。
通常,使用 synchronized 方法或语句比使用相互排斥的显式锁简单些。然而,使用显式锁对同步具有状态的线程更加直观和灵活。
线程间协作
锁上的条件可以用于协调线程之间的交互。
通过保证在临界区上的多个线程的相互排斥,线程同步完全可以避免竞争条件的发生。但是,可能会需要线程之间的相互协作。可以使用条件实现线程间通信。一个线程可以指定在某种条件下该做什么。
条件是通过调用 Lock 对象的 newCondition() 方法而创建的对象。一旦创建了条件,就可以使用 await() 、signal() 、signalAll() 方法来实现线程之间的相互通信。Condition 接口定义完成同步的方法:
<<interface>>
java.util.concurrent.Condition
| 接口方法 | 说明 |
|---|---|
| +await():void | 引起当前线程等待,直到发出条件信号 |
| +signal():void | 唤醒一个等待线程 |
| +signalAll():Condition | 唤醒所有等待线程 |
await() 方法可以让当前线程进入等待,直到条件发生。signal() 方法唤醒一个等待的线程。signalAll() 方法唤醒所有等待的线程。
从 Lock 对象中创建条件。为了使用条件,必须首先获取锁。await() 方法让线程等待并且自动释放条件上的锁。一旦条件正确,线程重新获取锁并且继续执行。
警告: 一旦线程调用条件上的 await() ,线程就进入等待状态,等待恢复的信号。如果忘记对等待状态的线程调用 signal() 或者 signalAll() ,那么线程就永远会等待下去。
警告: 条件有 Lock 对象创建。为了调用它的方法(如 await() 、signal() 、signalAll()),必须首先拥有锁。如果没有获取锁就调用这些方法,会抛出 IllegalMonitorStateException 异常。
锁和条件是 Java 5 中的新内容。在此之前,线程通信使用对象的内置监视器编程实现的。锁和条件比内置监视器强大灵活,无需使用监视器。
监视器(monitor)是一个相互排斥且具备同步能力的对象。监视器中的一个时间点上,只能有一个线程执行一个方法。线程通过获取监视器上的锁进入监视器,并且通过释放锁退出监视器。任意对象都可能是一个监视器。一旦一个线程锁住对象,该对象就成为监视器。加锁是通过在方法或块上使用 synchronized 关键字来实现的。在执行同步方法或块之前,线程必须获取锁。如果条件不适合线程继续在监视器内执行,线程可能在监视器中等待。可以对监视器对象调用 wait() 方法来释放锁,这样其它的监视器中的线程就可以获取它,也就可以改变监视器的状态。当条件合适时,另一线程可以调用 notify() 或 notifyAll() 方法来通知一个或所有的等待线程重新获取锁并且恢复执行。调用这些方法的模板如下:
sysnchronized (anObject){
try{
//Wait for the condition to become true
while(!contidion){
anObject.wait();
}
//Do something when condition is true
}catch(InterruptedException e){
e.printStackTrace();
}
}
synchronized (anObject){
//When condition become true
anObject.notify();
//anObject.notifyAll();
···
}
wait() 、notify() 、notifyAll() 方法协调线程间通信,它们必须在这些方法的接受对象的同步方法或同步块中调用。否则,就会出现 IllegalMonitorStateException 异常。
当调用 wait() 方法时,它终止线程同时释放对象的锁。当线程被通知之后重新启动时,锁就重新自动获取。
对象上的 wait() 、notify() 、notifyAll() 方法类似于条件上的 await() 、signal() 、signalAll() 方法。
阻塞队列
Java 集合框架提供了 ArrayBlockingQueue 、LinkedBlockingQueue 、PriorrityBlockingQueue 来支持阻塞队列。
阻塞队列(blocking queue)在试图向一个满队列添加元素或者从空队列中删除元素时会导致线程阻塞。BlockingQueue 接口继承了 java.util.Queue 接口,并且提供同步的 put 和 take 方法向队列尾部添加元素,以及从队列头部删除元素。
<<interface>>
java.long.Iterable<T>
<<interface>>
java.util.Collection<E>
extends Iterable<T>
<<interface>>
java.util.Queue<E>
extends Collection<E>
<<interface>>
java.util.concurrent.BlockingQueue<E>
extends Queue<E>
BlockingQueue 接口的同步方法:
| 接口方法 | 说明 |
|---|---|
| +put(element:E):void | 插入一个元素到队列的尾部,如果队列为满则等待 |
| +take():E | 从该队列的头部获取并删除元素,如果队列为空则等待 |
Java 支持三个具体的阻塞队列 ArrayBlockingQueue 、LinkedBlockingQueue 、PriorityBlockingQueue 。
java.util.concurrent.ArrayBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>,Serializable
java.util.concurrent.LinkedBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>,Serializable
java.util.concurrent.PriorityBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>,Serializable
ArrayBlockingQueue 、LinkedBlockingQueue 、PriorityBlockingQueue 是具体的阻塞队列。它们都定义在 java.util.concurrent 包中。
- ArrayBlockingQueue 使用数组实现阻塞队列,必须指定一个容量或者可选的公平性策略来构造 ArrayBlockingQueue 。
- LinkedBlockingQueue 使用链表实现阻塞队列。可以创建无边界的或有边界的 LinkedBlockingQueue 。
- PriorityBlockingQueue 是优先队列。可以创建无边界的或有边界的优先队列。
| 方法 |
|---|
| +ArrayBlockingQueue(capacity:int) |
| +ArrayBlockingQueue(capacity:int,fair:boolean) |
| +LinkedBlockingQueue() |
| +LinkedBlockingQueue(capacity:int) |
| +PriorityBlockingQueue() |
| +PriorityBlockingQueue(capacity:int) |
注意: 对于无边界的 LinkedBlockingQueue 和 PriorityBlockingQueue 而言,put 方法永远不会阻塞。
信号量
可以使用信号量来限制访问一个共享资源的线程数。
信号量指对共同资源进行访问控制的对象。在访问资源之前,线程必须从信号量获取许可。在访问完资源之后,这个线程必须将许可返回给信号量。
有限数量的线程可以访问信号量控制的共享资源。为了创建信号量,必须确定许可的数量,同时可选用公共策略。任务通过调用信号量的 acquire() 方法来获得许可,可以调用信号量的 release() 方法来释放许可。一旦获得许可,信号量可用许可的总数减一;一旦许可被释放,信号量可用许可的总数加一。
Semaphore 类包含访问信号量的方法:
| 方法 | 说明 |
|---|---|
| +Semaphore(numberOfPermits:int) | 创建一个具有指定数目的许可的信号量。公平性策略默认为假 |
| +Semaphore(numberOfPermits:int,fair:boolean) | 创建一个具有指定数目以及公平性策略的许可的信号量 |
| +acquire():void | 从该信号量获取一个许可。如果许可不可用,线程将被阻塞,直到一个许可可用 |
| +release():void | 释放一个许可给信号量 |
提示: 只有一个许可的信号量可以用来模拟一个相互排斥的锁。总是将 release() 方法放到 finally 子句中可以确保即使发生异常也能最终释放该许可。
避免死锁
可以采用正确的资源排序来避免死锁。
有时两个或多个线程需要在几个共享对象上获取锁,这可能会导致死锁。就是说,每个线程已经获取了其中一个对象上的锁,而且正在等待另一个对象上的锁。这样每个线程都在等待另一个线程释放它所需要的锁,结果导致两个或多个线程都无法继续运行,
使用一种称为资源排序的简单技术可以轻易避免死锁的发生。该技术是给每一个需要锁的对象指定一个顺序,确保每个线程都按照这个顺序来获取锁。
线程状态
线程状态可以表明一个线程的状态。
任务在线程中执行。线程可以是以下五种状态之一:新建、就绪、运行、阻塞、结束。
- 新建一个线程时,它就进入新建状态(New)。
- 调用线程的 start() 方法启动线程后,它进入就绪状态(ready)。就绪线程是可运行的,但可能还没有开始运行。操作系统必须为它分配 CPU 时间。
- 就绪线程开始运行时,它就进入运行状态。如果给定的 CPU 时间用完或者调用线程的 yield() 方法,处于运行状态的线程可能就进入就绪状态。
- 有几种原因可能使线程进入阻塞状态(非活动状态)。可能是它自己调用了 join() 、sleep() 或 wait() 方法。也可能是它在等待 I/O 操作的完成。当使得处于非激活状态的动作不起作用时,阻塞线程可能会被重新激活。
- 最后,如果一个线程执行完它的 run() 方法,这个线程就被结束(Finished)。
isAlive() 方法是用来判断线程状态的方法。如果线程处于就绪、阻塞或运行状态,则返回 true ;如果线程处于新建并且没有被启动的状态,或者已经结束,则返回 false 。
方法 interupt() 按下列方式中断一个线程:当线程当前处于就绪或运行状态时,就给它设置一个中断标志;当线程处于阻塞状态时,它就被唤醒并进入就绪状态,同时抛出异常 java.lang.IntegerruptException 。
同步合集
Java 合集框架为线性表、集合和映射表。
Java 合集框架中的类不是线程安全的。如果它们同时被多个线程访问和更新,它们的内容可能被破坏。可以通过锁定合集或者同步合集来保护合集中的数据。
Collections 类中提供 6 个静态方法来将合集转成同步版本,使用这些方法创建的合集称为同步包装类。
java.util.Collections
extends Object
| 方法 | 说明 |
|---|---|
| +synchronizedCollection(c:Collection):Collection | 从一个给定的合集返回一个同步的合集 |
| +synchronizedList(list:List):List | 从一个给定的线性表返回一个同步的线性表 |
| +synchronizedMap(m:Map):Map | 从一个给定的映射表返回一个同步的映射表 |
| +synchronizedSet(s:Set):Set | 从一个给定的集合返回一个同步的集合 |
| +synchronizedSortedMap(s:SortedMap):SortedMap | 从一个给定的排序映射表返回一个同步的排序映射表 |
| +synchronizedSortedSet(s:SortedSet):SortedSet | 从一个给定的排序集合返回一个同步的排序集合 |
同步合集可以很安全地被多个线程并发地同时访问和修改。调用 synchronizedCollection(Collection c) 会返回一个新的 Collection 对象,在它里面所有访问和更新原来的合集 c 的方法都被同步。这些方法使用 synchronized 关键字来实现。
注意: 在 java.util.Vector 、java.util.Stack 、java.util.Hashtable 中的方法已经被同步。从 JDK1.5 开始,应该使用 java.util.ArrayList 替换 Vector ,用 java.util.LinkedList 替换 Stack ,用 java.util.Map 替换 Hashtable 。如果需要同步,使用同步包装类。
同步包装类都是线程安全的。但是迭代器具有快速失效的特性,当使用一个迭代器对一个合集进行遍历,而其依赖的合集被另一个线程修改时,迭代器会抛出异常 java.util.ConcurrentModificationException 报错,该异常是 RuntimeException 的一个子类。为了避免错误,需要创建一个同步合集对象,并且在遍历它时获取对象上的锁。
并行编程
Fork/Join 框架用于在 Java 中实现并行编程。
分而治之方法的并行实现:一个问题分为不重叠的子问题,这些子问题可以并行地独立解决。然后合并所有子问题的解答获得问题的整体解答。
JDK7 引入新的 Fork/Join 框架用于并行编程,从而利用多核处理器。在 Fork/Join 框架中,一个分解(fork)可以视为运行在一个线程上的独立任务。
Fork/Join 框架使用 ForkJoinTask 类定义一个任务,同时在一个 ForkJoinPool 的实例中执行一个任务。
ForkJoinTask 类定义了一个用于异步执行的任务:
<<interface>>
java.util.concurrent.Future<V>
| 接口方法 | 说明 |
|---|---|
| +cancel(interrupt:boolean):boolean | 试图取消该任务 |
| +get():V | 如果需要,等待计算结束并返回一个结果 |
| +isDone():boolean | 如果任务完成,则返回 true |
java.util.concurrent.ForkJoinTask<V>
extends Object
implements Future<V>,Serializable
| 方法 | 说明 |
|---|---|
| +adapt(task:Runnable):ForkJoinTask | 从一个运行的任务处返回一个 ForkJoinTask |
| +fork():ForkJoinTask | 安排一个任务的异步执行 |
| +join():V | 当计算完成的时候,返回该计算的结果 |
| +invoke():V | 执行任务并等待完成,并且返回其结果 |
| +invokeAll(tasks:ForkJoinTask,…) | 分解给定的任务,并在所有任务都完成的时候返回 |
java.util.concurrent.RecursiveAction<V>
extends ForkJoinTask<V>
| 方法 | 说明 |
|---|---|
| #compute():void | 定义任务是如何执行的 |
java.util.concurrent.RecursiveTask<V>
extends ForkJoinTask<V>
| 方法 | 说明 |
|---|---|
| #compute():V | 定义任务如何执行的,当任务完成是返回值 |
ForkJoinPool 执行 Fork/Join 任务:
<<interface>>
java.util.concurrent.ExecutorService
extends Executor
java.util.concurrent.ForkJoinPool
extends ExecutorService
| 方法 | 说明 |
|---|---|
| +ForkJoinPool() | 使用所有可用的处理器来创建一个 ForkJoinPool |
| +ForkJoinPool(parallelism:int) | 使用指定数量的处理器来创建一个 ForkJoinPool |
| +invoke(ForkJoinTask:T | 执行任务,并在结束的时候返回其结果 |
ForkJoinTask 是用于任务的抽象基类。一个 ForkJoinTask 是一个类似线程的实体,但是比普通的线程要轻量级,因为巨量的任务和子任务可以被 ForkJoinPool 中的少数真正的线程所执行。任务主要使用 fork() 和 join() 来协调。在一个任务上调用 fork() 会安排异步的执行,然后调用 join() 等待任务完成。invoke() 和 invokeAll(tasks) 方法都隐式地调用 fork() 来执行,以及 join() 等待任务完成,如果有结果则返回结果。注意,静态 invokeAll 使用…语法来采用一个变长度的 ForkJoinTask 参数。
Fork/Join 框架是设计用于并行地分而治之解决方案,分而治之本身是递归的。RecursiveAction 和 RecursiveTask 是 ForkJoinTask 的两个子类。要定义具体的额任务类,类应该继承自 RecursiveAction 或 RecursiveTask 。RecursiveAction 用于不返回值的任务,而 RecursiveTask 用于返回值的任务。

本文详细介绍了Java中的多线程和并行程序设计,包括线程的概念、创建线程、Thread类、线程池、线程同步、加锁同步、线程间协作以及避免死锁等关键概念和技术。文中还提到了线程状态、同步合集以及Java的Fork/Join框架,旨在帮助开发者理解并有效利用多线程提高程序的执行效率。
7142

被折叠的 条评论
为什么被折叠?



