AQS(CountdownLatch、CyclicBarrier、Semaphore)、FutureTask、BlockingQueue、ForkJoin

1. J.U.C - AQS (AbstractQueuedSynchronizer)

java.util.concurrent(J.U.C) 大大提高了并发性能,AQS 被认为是 J.U.C 的核心。

1.1 CountdownLatch

用来控制一个线程等待多个线程。

维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒

public class CountdownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        final int totalThread = 10;
        CountDownLatch countDownLatch = new CountDownLatch(totalThread);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalThread; i++) {
            executorService.execute(() -> {
                System.out.print("run..");
                countDownLatch.countDown();
            });
        } 
        countDownLatch.await();
        System.out.println("end");
        executorService.shutdown();
    }
}

运行结果:

1.2 CyclicBarrier

用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行

和 CountdownLatch 相似,都是通过维护计数器来实现的。

  • 线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 awati() 方法而在等待的线程才能继续执行。

CyclicBarrier 和 CountdownLatch 的一个区别是:

  • CyclicBarrier 的计数器通过调用reset() 方法可以循环使用,所以它才叫做循环屏障

CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction在所有线程都到达屏障的时候会执行一次。

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0)
         throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
} 

public CyclicBarrier(int parties) {
    this(parties, null);
}
public class CyclicBarrierExample {
    public static void main(String[] args) {
        final int totalThread = 10;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalThread; i++) {
            executorService.execute(() -> {
                System.out.print("before..");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                } 
                System.out.print("after..");
            });
        } 
        executorService.shutdown();
    }
}

运行结果:

1.3 Semaphore

Semaphore 就是操作系统中的信号量,可以控制对互斥资源的访问线程数

以下代码模拟了对某个服务的并发请求,每次只能有 3 个客户端同时访问,请求总数为 10。

public class SemaphoreExample {
    public static void main(String[] args) {
        final int clientCount = 3;
        final int totalRequestCount = 10;
        Semaphore semaphore = new Semaphore(clientCount);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalRequestCount; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    System.out.print(semaphore.availablePermits() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            });
        } 
        executorService.shutdown();
    }
}

运行结果:

2. J.U.C - 其它组件

2.1 FutureTask

在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进行封装。

FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V>

FutureTask 可用于异步获取执行结果或取消执行任务的场景。

当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,主线程在完成自己的任务之后再去获取结果。

public class FutureTaskExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 0; i < 100; i++) {
                    Thread.sleep(10);
                    result += i;
                } 
                return result;
            }
        });
        Thread computeThread = new Thread(futureTask);
        computeThread.start();
        Thread otherThread = new Thread(() -> {
            System.out.println("other task is running...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        otherThread.start();
        System.out.println(futureTask.get());
    }
}

运行结果:

2.2 BlockingQueue

java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:

  • FIFO 队列 :LinkedBlockingQueue、ArrayBlockingQueue(固定长度)
  • 优先级队列 :PriorityBlockingQueue提供了阻塞的 take() 和 put() 方法:
    • 如果队列为空 take() 将阻塞,直到队列中有内容;
    • 如果队列为满 put() 将阻塞,直到队列有空闲位置。

使用 BlockingQueue 实现生产者消费者问题:

public class ProducerConsumer {
	private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
	private static class Producer extends Thread {
		@Override
		public void run() {
			try {
				queue.put("product");
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
			System.out.print("produce..");
		}
	}

	private static class Consumer extends Thread {
		@Override
		public void run() {
			try {
				String product = queue.take();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
			System.out.print("consume..");
		}
	}
}

public static void main(String[] args) {
    for (int i = 0; i < 2; i++) {
        Producer producer = new Producer();
        producer.start();
    } 
    for (int i = 0; i < 5; i++) {
        Consumer consumer = new Consumer();
        consumer.start();
    } 
    for (int i = 0; i < 3; i++) {
        Producer producer = new Producer();
        producer.start();
    }
}

运行结果:

2.3 ForkJoin

主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。

public class ForkJoinExample extends RecursiveTask<Integer> {
	private final int threshold = 5;
	private int first;
	private int last;

	public ForkJoinExample(int first, int last) {
		this.first = first;
		this.last = last;
	}

	@Override
	protected Integer compute() {
		int result = 0;
		if (last - first <= threshold) {
			// 任务足够小则直接计算
			for (int i = first; i <= last; i++) {
				result += i;
			}
		} else {
			// 拆分成小任务
			int middle = first + (last - first) / 2;
			ForkJoinExample leftTask = new ForkJoinExample(first, middle);
			ForkJoinExample rightTask = new ForkJoinExample(middle + 1, last);
			leftTask.fork();
			rightTask.fork();
			result = leftTask.join() + rightTask.join();
		}
		return result;
	}
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
	ForkJoinExample example = new ForkJoinExample(1, 10000);
	ForkJoinPool forkJoinPool = new ForkJoinPool();
	Future result = forkJoinPool.submit(example);
	System.out.println(result.get());
}

ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于CPU 核数。

public class ForkJoinPool extends AbstractExecutorService

ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。

  • 每个线程都维护了一个双端队列,用来存储需要执行的任务。
  • 工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。
  • 窃取的任务必须是最晚的任务,避免和队列所属线程发生竞争。但是如果队列中只有一个任务时还是会发生竞争。

猜你喜欢

转载自blog.csdn.net/Zhxin606a/article/details/89436205