背景
ReentrantLock类
基本使用
基本特点
tryLock使用
总结
ReadWriteLock接口
特点
基本使用
适用条件
总结
Condition接口
基本使用
方法原理
总结
Concurrent集合
基本使用
Blocking集合
总结
Atomic包
使用举例
总结
ExecutorService线程池
线程池
常用ExecutorService
ScheduledThreadPool
模式
思考
Timer
总结
Future
Callable
Future接口
总结
CompletableFuture类
基本使用
优点
用法详解
1、创建对象
2、设置回调
3、主线程等待
多任务串行执行
多任务并行执行
anyOf
allOf
CompletableFuture方法命名规则
总结
Fork / Join
案例
总结
前面已经提到,Java提供了synchronized/wait/notify等方法来解决多线程竞争和协调问题,但是编写多线程的同步依然比较困难,步骤很负责。在JDK1.5开始Java提供了一个高级的concurrent包来处理多线程问题
java.util.concurrent
使用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();
}
}
}
class Counter {
// 创建ReentrantLock对象
final Lock lock = new ReentrantLock();
public void add() {
// 加锁,并指定超时时间
if (lock.tryLock(1,TimeUnit.SECONDS)) {
try {
n = n + 1;
} finally {
// 释放锁。
lock.unlock();
}
}
}
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可以提高读取效率
前面已经提到,使用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一致
前面的案例中,从一个队列中读取数据时,如果没有数据则需要等待,这种情况下的队列被称为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");
}
}
使用concurrent提供的Blocking集合可以简化多线程编程
java.util.concurrent.atomic提供了一组原子类型操作:
Atomic类可以实现
class IdGenerator {
AtomicLong var = new AtomicLong(0);
/*
多线程安全的ID序列生成器
*/
public long getNextId() {
// 加1并返回值
return var.incrementAndGet();
}
}
使用java.util.atomic提供的原子操作可以简化多线程编程:
创建线程会消耗系统资源,而且频繁创建和销毁线程需要消耗大量时间,如果能够复用线程将大大提高运行效率,降低资源消耗,因此线程池应运而生。
JDK提供了ExecutorService接口表示线程池
ExecutorService executor = Executors.newFixedThreadPool(4);// 固定大小的线程池
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
...
// 固定线程数的线程池
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>());
可以定期反复执行一个任务的线程池
// 创建一个定期执行的线程池,并指定维持的线程数量
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
// 参数一:执行的任务
// 参数二:多长时间后开始执行任务
// 参数三:每隔多久执行一次任务
// 参数四:时间单位
// 1秒后开始执行任务,而且每3秒执行一次
executor.scheduleAtFixedRate(new Thread(), 1, 3, TimeUnit.SECONDS);
// 创建一个定期执行的线程池,并指定维持的线程数量
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
// 参数一:执行的任务
// 参数二:多长时间后开始执行任务
// 参数三:两次任务的间隔
// 参数四:时间单位
// 1秒后开始执行任务,上一次任务结束后3秒执行下一次任务
executor.scheduleWithFixedDelay(new Task("002"), 1, 3, TimeUnit.SECONDS);
java.util.Timer
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";
}
}
表示一个任务未来可能会返回的结果
Callable<String> Task = new Task();
ExecutorService executor = Executors.newFixedThreadPool(4);
// 接收任务执行结果对象
Future<String> future = executor.submit(task);
// 获取具体的返回值,如果任务还没结束会阻塞,一直到任务执行结束返回结果
String result = future.get();
使用Future获取异步执行结果的方法:
以上两种方式效率都比较低,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;
});
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()方法执行具体任务。
// 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;
}
});
// 注意:主线程结束时默认使用的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执行完 --> 获取结果
多个任务中只要有一个任务结束就获取结果
// 创建两个任务实例
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(Supplier1);
CompletableFuture<Float> cf2 = CompletableFuture.supplyAsync(Supplier2);
// 通过anyOf转化为新的实例,注意修改范型类型
CompletableFuture<Object> cf3 = CompletableFuture.anyOf(cf1, cf2);
// 通过新的实例获取结果
cf3.thenAccept(运行结果操作);
cf3.join();
多个任务全部执行结束后才会获取结果
// 创建两个任务实例
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对象可以指定异步处理流程:
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);
}
}