Fork-Join
核心思想
分而治之:将一个大问题分割为相同的小问题,小问题之间无关联
快速排序、归并排序、二分查询都属于分而治之的思想
Fork-Join的工作密取
将一个大问题分割为相同的小问题交给多个线程执行,当一个线程先处理完任务之后,自动到其它线程的Task池中取出任务继续执行。
ForkJoinPool 中维护着多个线程(一般为CPU 核数)在不断地执行Task,每个线程除了执行自己职务内的Task 之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,这样就能能够减少线程阻塞或是闲置的时间,提高CPU 利用率。
RecursiveTask:有返回值的任务
public class ForkJoinTest {
// 数组大小
private static final Integer ARR_LENGTH = 5000;
// 每个子任务大小
private static final Integer FORK_LENGTH = ARR_LENGTH / 100;
private static int[] arr = new int[ARR_LENGTH];
static{
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
// RecursiveTask 有返回值的任务
private static class Sum extends RecursiveTask<Integer>{
private int start, end;
private int[] arrSum;
// 构造
public Sum(int[] arrSum, int start, int end) {
this.arrSum = arrSum;
this.start = start;
this.end = end;
}
// 重写核心方法
@Override
protected Integer compute() {
if (end - start < FORK_LENGTH) {
int count = 0;
for (int i = start; i <= end; i++) {
SleepTools.ms(1);
count += arr[i];
}
return count;
}else{
int mid = (start + end) / 2;
Sum left = new Sum(arrSum, start, mid);
Sum right = new Sum(arrSum, mid + 1, end);
invokeAll(left, right);
return left.join() + right.join();
}
}
}
public static void main(String[] args) {
long start1 = System.currentTimeMillis();
int result1 = 0;
for (int i = 0; i < arr.length; i++) {
SleepTools.ms(1);
result1 += arr[i];
}
long end1 = System.currentTimeMillis();
System.out.println("总和:" + result1 + "\t普通方式用时:" + (end1 - start1) + "毫秒");
System.out.println("----------------------------------------------------------------");
// ForkJoin线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
Sum sum = new Sum(arr, 0, arr.length-1);
long start2 = System.currentTimeMillis();
// invoke() 同步执行,带返回值,tasks会被同步到主线程
// submit() 异步执行,带返回值,可通过task.get实现同步到主线程
forkJoinPool.invoke(sum);
Integer result2 = sum.join();
long end2 = System.currentTimeMillis();
System.out.println("总和:" + result2 + "\tForkJoin用时:" + (end2 - start2) + "毫秒");
}
}
/*
总和:12497500 普通方式用时:8834毫秒
----------------------------------------------------------------
总和:12497500 ForkJoin用时:1148毫秒
*/
RecursiveAction:没有返回值的任务
// 没有返回值的任务
public class ForkJoinTest extends RecursiveAction {
private File file;
public ForkJoinTest(File file) {
this.file = file;
}
@Override
protected void compute() {
List<FindDirsFiles> listDirs = new ArrayList<>();
File[] files = file.listFiles();
if (files!=null){
for (File file : files) {
if (file.isDirectory()) {
// 对每个子目录都新建一个子任务。
listDirs.add(new FindDirsFiles(file));
} else {
// 遇到文件,检查。
if (file.getAbsolutePath().endsWith("txt")){
System.out.println("文件:" + file.getAbsolutePath());
}
}
}
if (!listDirs.isEmpty()) {
// 在当前的 ForkJoinPool 上调度所有的子任务。
for (FindDirsFiles subTask : invokeAll(listDirs)) {
subTask.join();
}
}
}
}
public static void main(String[] args) {
// ForkJoin线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTest forkJoinTest = new ForkJoinTest(new File("F:/"));
// 异步提交,无返回值
forkJoinPool.execute(forkJoinTest);
SleepTools.ms(1);
}
}
CountDownLatch
CountDownLatch能够使一个线程等待其他线程完成各自的工作后再执行。
CountDownLatch通过一个计数器来实现的,计数器初始值为初始任务的数量或少于初始任务的数量[因为每个任务可以多次修改计数器]。每当完成了一个任务后,计数器减1[CountDownLatch.countDown()]。当计算器值为0时,在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。
可以实现最大的并行性。
CyclicBarrier
CyclicBarrier可以让一组线程达到一个同步点时被阻塞,直到这一组线程中的最后一个线程达到这个同步点时,阻塞才会自释放。
CyclicBarrier有两个构造函数public CyclicBarrier(int parties)和public CyclicBarrier(int parties, Runnable barrierAction)。
parties参数表示拦截的线程数量;barrierAction参数表示优先执行的线程。
Semaphore
Semaphore信号量是用来控制同时访问特定资源的线程数量,它通过协调各个线程,保证合理使用公共资源。
Semaphore构造方法接受一个整形数字,表示可用的许可证数量。
acquire():获取一个许可证。
release():归还一个许可证。
tryAcquire():尝试获取许可证。
intavailablePermits():返回信号量中当前可用的许可证数。
intgetQueueLength():返回正在等待获取许可证的线程数。
booleanhasQueuedThread():是否有线程正在等待获取许可证。
reducePermits(int reduction):减少reduction个许可证,是protected方法。
getQueuedThread():返回所有等待获取许可证的线程集合,是protected方法。
public class SemaphoreTest {
private static final int THREAD_NUM = 100;
private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(THREAD_NUM + 1);
// 初始5个信号
private static final Semaphore init = new Semaphore(5), used = new Semaphore(0);
// 初始5个资源
private static LinkedList<Object> list = new LinkedList<Object>() {{
add(new Object());
add(new Object());
add(new Object());
add(new Object());
add(new Object());
}};
public static void returnObject(Object object) throws Exception {
if (null != object) {
System.out.println("当前有" + init.getQueueLength() + "个线程在等待获取object -- 可用信号数:" + init.availablePermits());
used.acquire();
synchronized (list) {
list.addLast(object);
}
init.release();
}
}
public static Object getObject() throws Exception {
Object o = null;
init.acquire();
synchronized (list) {
o = list.removeFirst();
}
used.release();
return o;
}
private static class ChdThread implements Runnable {
@Override
public void run() {
try {
countDownLatch.await();
long start = System.currentTimeMillis();
Object object = getObject();
System.out.println(Thread.currentThread().getName() + "获取对象耗时:" + (System.currentTimeMillis() - start) + "毫秒");
SleepTools.ms(new Random().nextInt(100) + 100);
returnObject(object);
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
System.out.println("List初始大小:" + list.size());
for (int i = 0; i < THREAD_NUM; i++) {
Thread thread = new Thread(new ChdThread());
thread.setName("第" + i + "个线程");
thread.start();
countDownLatch.countDown();
}
cyclicBarrier.await();
System.out.println("List最终大小:" + list.size());
}
}
Callable、Future、FutureTask
Runnable接口的run()方法无返回值,所以线程执行之后无法返回结果。
Callable接口的call()方法可以返回传递进来的泛型。
Future接口是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。可以通过get()方法获取执行结果,get()方法会阻塞直到任务返回。
FutureTask类是Future接口的实现
FutureTask类实现了RunnableFuture接口;RunnableFuture接口继承了Runnable接口和Future接口。所以FutureTask即可以作为Runnable线程被执行,又可以作为Future得到Callable的返回值。
_____个人笔记_____((≡^⚲͜^≡))_____欢迎指正_____