背景
ReentrantLock类
基本使用
基本特点
tryLock使用
总结
ReadWriteLock接口
特点
基本使用
适用条件
总结
Condition接口
基本使用
方法原理
总结
Concurrent集合
基本使用
Blocking集合
总结
Atomic包
使用举例
总结
ExecutorService线程池
线程池
常用ExecutorService
ScheduledThreadPool
模式
思考
Timer
总结
Future
Callable<T>接口
Future接口
总结
CompletableFuture类
基本使用
优点
用法详解
1、创建对象
2、设置回调
3、主线程等待
多任务串行执行
多任务并行执行
anyOf
allOf
CompletableFuture方法命名规则
总结
Fork / Join
案例
总结
背景
前面已经提到,Java提供了synchronized/wait/notify等方法来解决多线程竞争和协调问题,但是编写多线程的同步依然比较困难,步骤很负责。在JDK1.5开始Java提供了一个高级的concurrent包来处理多线程问题
java.util.concurrent
ReentrantLock类
基本使用
使用ReentrantLock可以替代synchronized
class Counter {
// 创建ReentrantLock对象(实现了Lock接口)
final Lock lock = new ReentrantLock();
public void add() {
// 加锁,要写在try之前,因为可能会失败
lock.lock();
try {
n = n + 1;
} finally {
// 释放锁。为了保证一定能释放锁,必须使用try...finally...
lock.unlock();
}
}
}
基本特点
- 可重入锁,一个线程可多次获取同一个锁
- lock()方法可获取锁
- tryLock() 方法可尝试获取锁并可指定超时时间
tryLock使用
class Counter {
// 创建ReentrantLock对象
final Lock lock = new ReentrantLock();
public void add() {
// 加锁,并指定超时时间
if (lock.tryLock(1,TimeUnit.SECONDS)) {
try {
n = n + 1;
} finally {
// 释放锁。
lock.unlock();
}
}
}
总结
- ReentrantLock可以替代synchronized
- ReentrantLock获取锁更安全
- 必须使用try… finally保证正确获取锁和释放锁
ReadWriteLock接口
特点
- 只允许一个线程写入(其他线程既不能写入也不能读取)
- 没有写入时,多个线程允许同时读(提高性能)
基本使用
class Counter {
// 创建ReadWriteLock接口的实现类对象
final ReadWriteLock lock = new ReentrantReadWriteLock();
// 获取读锁
final Lock rLock = lock.readLock();
// 获取写锁
final Lock wLock = lock.writeLock();
// 写方法
public void add() {
// 使用写锁来加锁
wLock.lock();
try {
value += 1;
} finally {
wLock.unlock();
}
}
// 读方法
public void get() {
// 使用读锁来加锁
rLock.lock();
try {
return this.value;
} finally {
rLock.unlock();
}
}
}
适用条件
- 同一个实例,有大量线程读取,仅有少数线程修改(比如文章的评论)
总结
使用ReadWriteLock可以提高读取效率
- ReadWriteLock只允许一个线程写入
- ReadWriteLock允许多个线程同时读取
- ReadWriteLock适合读多写少的场景
Condition接口
前面已经提到,使用ReentrantLock可以替代synchronized,但是不能直接实现wait和notify的功能,Java提供了一个Condition接口来配合ReentrantLock来实现wait和notify功能。
基本使用
class TaskQueue {
final Lock lock = new ReentrantLock();
// condition对象必须从ReentrantLock获取
final Condition condition = lock.newCondition();
public String getTask() {
lock.lock();
try {
while (this.queue.isEmpty()) {
// 线程等待方法 await()
// 等同于synchronized的wait()方法
condition.await();
}
return queue.remove();
} finally {
lock.unlock();
}
}
public void addTask(String name) {
lock.lock();
try {
this.queue.add(name);
// 唤醒方法 signal/signalAll
// 等同于synchronized的notify/notifyAll
condition.signalAll();
} finally {
lock.unlock();
}
}
}
方法原理
Condition中的await / signal / signalAll原理和 synchronized中的wait / notifu/ notifyAll一致
- await()会释放当前锁,进入等待状态
- signal()会唤醒某个等待线程
- signalAll()会唤醒所有等待线程
- 唤醒线程从await()返回后需要重新获得锁
总结
- Condition可以替代wait / notify
- Condition对象必须从ReentrantLock对象获取
- ReentrantLock+Condition可以替代synchronized + wait / notify
Concurrent集合
前面的案例中,从一个队列中读取数据时,如果没有数据则需要等待,这种情况下的队列被称为Blocking Queue。Java提供了线程安全的Blocking Queue来简化开发。
基本使用
class WorkerThread extends Thread {
BlockingQueue<String> taskQueue;
public WorkerThread(BlockingQueue<String> taskQueue) {
this.taskQueue = taskQueue;
}
@Override
public void run() {
while (!isInterrupted()) {
String name;
try {
// 从队列中取数据,如果没有数据就会进行等待
name = taskQueue.take();
} catch (InterruptedException e) {
break;
}
String result = "Hello, " + name + "!";
System.out.println(result);
}
}
}
public class Main {
public static void main(String[] args) throws Exception {
BlockingQueue<String> taskQueue = new ArrayBlockingQueue<>(100);
WorkerThread worker = new WorkerThread(taskQueue);
worker.start();
taskQueue.put("Alice");
Thread.sleep(1000);
taskQueue.put("Bob");
Thread.sleep(1000);
taskQueue.put("Tim");
Thread.sleep(1000);
worker.interrupt();
worker.join();
System.out.println("END");
}
}
Blocking集合
总结
使用concurrent提供的Blocking集合可以简化多线程编程
- 多线程同时访问Blocking集合是安全的
- 尽量使用JDK提供的concurrent集合,避免自己编写同步代码
Atomic包
java.util.concurrent.atomic提供了一组原子类型操作:
- AtomicInteger
- int addAndGet(int delta)
- int incrementAndGet()
- int get()
- int compareAndSet(int expect, int update)
Atomic类可以实现
- 无锁(lock-free)实现的线程安全(thread-safe)访问
使用举例
class IdGenerator {
AtomicLong var = new AtomicLong(0);
/*
多线程安全的ID序列生成器
*/
public long getNextId() {
// 加1并返回值
return var.incrementAndGet();
}
}
总结
使用java.util.atomic提供的原子操作可以简化多线程编程:
- AtomicInteger / AtomicLong / AtomicIntegerArray 等
- 原子操作实现了无锁的线程安全
- 适用于计数器, 累加器等
ExecutorService线程池
创建线程会消耗系统资源,而且频繁创建和销毁线程需要消耗大量时间,如果能够复用线程将大大提高运行效率,降低资源消耗,因此线程池应运而生。
线程池
- 线程池维护若干个线程,处于等待状态
- 如果有新任务,就分配一个空闲线程执行
- 如果所有线程都处于忙碌状态,新任务放入队列等待
JDK提供了ExecutorService接口表示线程池
ExecutorService executor = Executors.newFixedThreadPool(4);// 固定大小的线程池
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
...
常用ExecutorService:
- FixedThreadPool:线程数固定
- CachedThreadPool:线程数根据任务数动态调整
- SingleThreadExecutor:仅单线程执行,只创建一个线程
// 固定线程数的线程池
ExecutorService executor1 = Executors.newFixedThreadPool(10);
// 线程数根据任务数量动态调整的线程池
ExecutorService executor2 = Executors.newCachedThreadPool();
// 单线程的线程池
ExecutorService executor3 = Executors.newSingleThreadExecutor();
虽然CachedThreadPool不可可以设置固定的线程数,但是查看其源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
其中ThreadPoolExecutor方法的第二个参数为线程数量,因此可以通过创建这个对象来创建固定线程数量的线程池
ExecutorService executor = new ThreadPoolExecutor(0, 5,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
ScheduledThreadPool
可以定期反复执行一个任务的线程池
模式
- Fixed Rate 定期多久执行一次任务,不管任务执行多久
// 创建一个定期执行的线程池,并指定维持的线程数量
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
// 参数一:执行的任务
// 参数二:多长时间后开始执行任务
// 参数三:每隔多久执行一次任务
// 参数四:时间单位
// 1秒后开始执行任务,而且每3秒执行一次
executor.scheduleAtFixedRate(new Thread(), 1, 3, TimeUnit.SECONDS);
- Fixed Delay 一次任务结束后间隔多久执行下一次任务
// 创建一个定期执行的线程池,并指定维持的线程数量
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
// 参数一:执行的任务
// 参数二:多长时间后开始执行任务
// 参数三:两次任务的间隔
// 参数四:时间单位
// 1秒后开始执行任务,上一次任务结束后3秒执行下一次任务
executor.scheduleWithFixedDelay(new Task("002"), 1, 3, TimeUnit.SECONDS);
思考
- FixedRate模式下,如果任务执行时间过长,后续任务会不会并发执行?(不会)
- 如果任务抛出了异常,后续任务是否继续执行?(会)
Timer
java.util.Timer
- 一个Timer对应一个Thread
- 必须在主线程结束时调用Timer.cancel()
总结
- JDK提供了ExecutorService实现了线程池功能
- 线程池内部维护一组线程,可以高效执行大量小任务
- Executors提供了静态方法创建不同类型的ExecutorService
- 必须调用shutdown()关闭ExecutorService
- ScheduledThreadPool可以定期调度多个任务
Future接口
Callable<T>接口
Callable和Runnable相似,但是Runnable没有返回值,Callable有返回值,所以实现Callable时需要指定范型,并重写call()方法指定返回值类型
// Callable接口源码
@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;
}
// 实现Callable接口
class Task implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello";
}
}
Future接口
表示一个任务未来可能会返回的结果
- get() 获取执行结果
- get(long timeout, TimeUnit unit) 指定最大等待结果时间
- cancel(boolean mayInterruptIfRunning) 中断异步任务的执行
- isDone() 判断异步任务是否完成
Callable<String> Task = new Task();
ExecutorService executor = Executors.newFixedThreadPool(4);
// 接收任务执行结果对象
Future<String> future = executor.submit(task);
// 获取具体的返回值,如果任务还没结束会阻塞,一直到任务执行结束返回结果
String result = future.get();
Runnable VS Callable
总结
- 提交Callable任务,可以获得一个Future对象
- 可以用Furure在将来某个时刻获取结果
CompletableFuture类
使用Future获取异步执行结果的方法:
- get():阻塞方法
- isDone():判断任务是否完成来轮询
以上两种方式效率都比较低,JDK提供了一个CompletableFuture类,可以通过设置回调方法的形式在任务结束后来获取结果。
基本使用
// 创建CompletableFuture对象,并指定范型(结果)类型
CompletableFuture<String> cf = ...
// thenAccept方法设置任务正常运行完成后的操作
cf.thenAccept(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("异步任务正常运行结果 : " + s);
}
});
// exceptionally方法设置任务发生异常的操作
cf.exceptionally(new Function<Throwable, String>() {
@Override
public String apply(Throwable throwable) {
System.out.println("运行发生异常 : " + throwable.getLocalizedMessage());
return null;
}
});
/************************* 分割线 ******************************/
// JDK1.8函数式编程写法(了解)
cf.thenAccept((result) -> {
System.out.println("异步任务正常运行结果 : " + s);
});
cf.exceptionally((t) -> {
System.out.println("运行发生异常");
return null;
});
优点
- 异步任务结束时,会自动回调某个对象的方法
- 异步任务出错时,会自动回调某个对象的方法
- 主线程设置好回调后,不再关心异步任务的执行
用法详解
1、创建对象
CompletableFuture<String> cf = CompletableFuture.supplyAsync(new ASupplier());
// Supplier接口源码
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
通过CompletableFuture的supplyAsync方法创建对象,需要传入一个Supplier实例对象,可以理解为任务对象,重写get()方法执行具体任务。
2、设置回调
// thenAccept方法设置任务正常运行完成后的操作
cf.thenAccept(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("异步任务正常运行结果 : " + s);
}
});
// exceptionally方法设置任务发生异常的操作
cf.exceptionally(new Function<Throwable, String>() {
@Override
public String apply(Throwable throwable) {
System.out.println("运行发生异常 : " + throwable.getLocalizedMessage());
return null;
}
});
3、主线程等待
// 注意:主线程结束时默认使用的Executor会关闭,所以要使用join方法等待任务执行完毕
cf.join();
多任务串行执行
// 实例1对象
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(Supplier1);
// cf1通过thenApplyAsync方法获取实例2对象
CompletableFuture<Float> cf2 = cf1.thenApplyAsync(Supplier2);
// cf2通过thenApplyAsync方法获取实例3对象
CompletableFuture<Integer> cf3 = cf2.thenApplyAsync(Supplier3);
// 通过实例3获取结果
cf3.thenAccept(实例3运行结果操作);
cf3.exceptionally(实例3运行异常操作);
// 多个任务都执行完后再获取结果:cf1执行完 --> cf2执行完 --> cf3执行完 --> 获取结果
多任务并行执行
anyOf
多个任务中只要有一个任务结束就获取结果
// 创建两个任务实例
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(Supplier1);
CompletableFuture<Float> cf2 = CompletableFuture.supplyAsync(Supplier2);
// 通过anyOf转化为新的实例,注意修改范型类型
CompletableFuture<Object> cf3 = CompletableFuture.anyOf(cf1, cf2);
// 通过新的实例获取结果
cf3.thenAccept(运行结果操作);
cf3.join();
allOf
多个任务全部执行结束后才会获取结果
// 创建两个任务实例
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(Supplier1);
CompletableFuture<Float> cf2 = CompletableFuture.supplyAsync(Supplier2);
// 通过allOf转化为新的实例,范型类型只能是Void
CompletableFuture<Void> cf3 = CompletableFuture.allOf(cf1, cf2);
// 通过新的实例获取结果
cf3.thenAccept(运行结果操作);
cf3.join();
CompletableFuture方法命名规则
- xxx():表示继续在已有的线程中执行
- xxxAsync():用Executor的新线程执行
总结
CompletableFuture对象可以指定异步处理流程:
- thenAccept() 处理正常结果
- exceptionally() 处理异常结果
- thenApplyAsync() 用于串行化另一个CompletableFuture对象
- anyOf / allOf 用于并行化多个CompletableFuture
Fork / Join
Fork/Join框架是Java7提供的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。使用工作窃取(work-stealing)算法,主要用于实现“分而治之”。
案例
// 任务类必须继承自RecursiveTask(有返回值) / RecursiveAction(没有返回值)
class SumTask extends RecursiveTask<Long> {
// 定义一个阀值,用于判断是否要进行拆分
static final int THRESHOLD = 500;
long[] array;
int start;
int end;
SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
// 执行任务方法
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// 如果任务足够小,直接计算:
long sum = 0;
for (int i = start; i < end; i++) {
sum += this.array[i];
try {
Thread.sleep(2);
} catch (InterruptedException e) {
}
}
return sum;
}
// 任务太大,一分为二:
int middle = (end + start) / 2;
System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
// 分成两个小任务
SumTask subtask1 = new SumTask(this.array, start, middle);
SumTask subtask2 = new SumTask(this.array, middle, end);
// 并行执行两个任务
invokeAll(subtask1, subtask2);
// 分别获取结果
Long subresult1 = subtask1.join();
Long subresult2 = subtask2.join();
// 得出最终结果
Long result = subresult1 + subresult2;
System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
return result;
}
}
public class ForkJoinTaskSample {
public static void main(String[] args) throws Exception {
// 创建1000个随机数组成的数组:
long[] array = new long[1000];
long expectedSum = 0;
for (int i = 0; i < array.length; i++) {
array[i] = random();
expectedSum += array[i];
}
System.out.println("Expected sum: " + expectedSum);
// fork/join:
ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
long startTime = System.currentTimeMillis();
// 执行任务
Long result = ForkJoinPool.commonPool().invoke(task);
long endTime = System.currentTimeMillis();
System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}
static Random random = new Random(0);
static long random() {
return random.nextInt(10000);
}
}
总结
- Fork / Join是一种基于“分治”的算法:
- 分解任务 + 合并结果
- ForkJoinPool线程池可以把一个大任务分拆成小任务并行执行
- 任务类必须继承自RecursiveTask(有返回值) / RecursiveAction(无返回值)
- 使用Fork / Join模式可以进行并行计算提高效率