java并发编程笔记(六)——AQS
使用了Node实现FIFO(first in first out)队列,可以用于构建锁或者其他同步装置的基础框架
利用了一个int类型表示状态
使用方法是继承
子类通过继承并通过实现它的方法管理其状态(acquire和release)的方法操纵状态
可以同时实现排它锁和共享锁模式(独占、共享)
AQS同步组件
- CountDownLatch
- Semaphore
- CyclicBarrier
- ReentrantLock
- Condition
- FutureTask
CountDownLatch
public class CountDownLatchExample1 {
private final static int threadCount = 200;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum);
} catch (Exception e) {
log.error("exception", e);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
#countDownLatch.await(10, TimeUnit.MILLISECONDS); //这种方式可以设置超时时间,如果指定时间未完成,就结束等待
log.info("finish");
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
Thread.sleep(100);
log.info("{}", threadNum);
Thread.sleep(100);
}
}
Semaphore
信号量,控制某个资源同时被访问的个数
1、适用于仅能提供有限资源访问的场景,如:数据库连接
//基本使用
public class SemaphoreExample1 {
private final static int threadCount = 20;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(); // 获取一个许可
//semaphore.acquire(3); // 获取多个许可
test(threadNum);
semaphore.release(); // 释放一个许可
//semaphore.release(); // 释放多个许可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
}
//尝试获取许可
public class SemaphoreExample3 {
private final static int threadCount = 20;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
if (semaphore.tryAcquire()) { // 尝试获取一个许可
test(threadNum);
semaphore.release(); // 释放一个许可
}
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
}
//在等待的时间内,尝试获取许可
public class SemaphoreExample4 {
private final static int threadCount = 20;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) { // 尝试获取一个许可
test(threadNum);
semaphore.release(); // 释放一个许可
}
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
}
CyclicBarrier
允许一组线程相互等待,直到到达某个公共的屏障点。
可以完成多个线程相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。
与CountDownLatch很相似,但存在几个差异点:
1、CountDownLatch是一次性的,用完就销毁了,CyclicBarrier可以通过reset()方法重置,重复使用。
2、CountDownLatch主要是实现一个或N个线程需要等待其他线程完成某项操作之后,才能继续往下执行;而CyclicBarrier主要是实现了多个线程之间相互等待,直到所有的线程都满足了条件之后才能继续后续的操作,它描述的是各个线程内部相互等待的关系。比如我们设置了初始值是5,只有当5个线程都达到某个条件了,才能继续往下执行。
//基本使用
public class CyclicBarrierExample1 {
private static CyclicBarrier barrier = new CyclicBarrier(5);
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await();
log.info("{} continue", threadNum);
}
}
public class CyclicBarrierExample2 {
private static CyclicBarrier barrier = new CyclicBarrier(5);
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
try {
barrier.await(2000, TimeUnit.MILLISECONDS); //设置等待超时时间
} catch (Exception e) {
log.warn("BarrierException", e);
}
log.info("{} continue", threadNum);
}
}
public class CyclicBarrierExample3 {
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
log.info("callback is running");
}); /线程达到屏障点的的时候,优先执行这个方法
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await();
log.info("{} continue", threadNum);
}
}
J.U.C相关的锁
ReentrantLock与锁
锁的种类:
- synchronized
- J.U.C中提供的锁,核心锁就是ReentrantLock
ReentrantLock(可重入锁)和synchronized区别
-
可重入性:前者是可重入锁,后者也是可重入锁,两者相差不大
-
锁的实现:后者是依赖JVM实现的,前者是JDK实现的
-
性能的区别:自从后者引入了轻量锁,偏向锁,自选锁后其性能与前者相差不大,由于后者使用方便,可读性高,建议使用后者。
-
功能的区别:
- 后者比前者便利,后者是通过编译器去控制加锁和释放锁的,而后者需要调用者手动去加锁和释放锁。
- 前者锁的细粒度比后者好。
-
ReentrantLock独有的功能(当需要实现下述这几种独有的功能时,必须使用ReentrantLock)
-
可以指定是公平锁还是非公平锁(sychronized只能是非公平锁);
-
提供了一个Condition类,可以分组唤醒需要唤醒的线程
public class LockExample6 { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Condition condition = reentrantLock.newCondition(); new Thread(() -> { try { reentrantLock.lock(); //这个时候线程就加入到了AQS的等待队列里去 log.info("wait signal"); // 1 condition.await(); //线程从AQS的等待队列里移除了,对应的操作其实是锁的释放,接着就加入到了condition的等待队列里去 } catch (InterruptedException e) { e.printStackTrace(); } log.info("get signal"); // 4 reentrantLock.unlock(); }).start(); new Thread(() -> { reentrantLock.lock(); //因为线程1释放的锁的缘故,所以这里可以取到锁 log.info("get lock"); // 2 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } condition.signalAll(); //发送信号,这个时候condition等待队列里有线程1的节点,执行完之后线程1从condition等待队列里取出来了,加入到了AQS的等待队列了 log.info("send signal ~ "); // 3 reentrantLock.unlock(); //线程2释放锁,因此线程1被唤醒 }).start(); } }
-
提供能够中断等待锁的线程的机制,lock.lockInterruptibly()
public class LockExample2 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static int count = 0; private final static Lock lock = new ReentrantLock(); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); add(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); } private static void add() { lock.lock(); try { count++; } finally { lock.unlock(); } } }
-
ReentranReadWriteLock锁
在没有任何读写锁的情况下,才可以取得写入的锁
@Slf4j
public class LockExample3 {
private final Map<String, Data> map = new TreeMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public Data get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public Set<String> getAllKeys() {
readLock.lock();
try {
return map.keySet();
} finally {
readLock.unlock();
}
}
public Data put(String key, Data value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
class Data {
}
}
StampedLock锁
控制锁有三种模式:
- 写
- 读
- 乐观读
如果读的操作很多,写的操作很少的情况下,我们可以乐观的认为写入和读取同时发生的概率很小,因此不悲观使用完全锁定;因此在性能上有很大的提升。
案例:
public class LockExample5 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
private final static StampedLock lock = new StampedLock();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
long stamp = lock.writeLock();
try {
count++;
} finally {
lock.unlock(stamp);
}
}
}
锁总结
- 当只有少量的竞争者的时候,sychrnozied是一个很好地锁实现
- 竞争者不少但是线程增长的趋势我们能够预估,这个时候ReetrantLock是一个很好的锁实现
FutureTask
Callable和Runnable接口的对比
callable:
在线程执行完之后,可以获取线程执行的结果;
有一个call()方法,有返回值;
Runnable:
只有run()方法
Future接口
查询任务的执行情况,可以监视线程的执行情况,如果调用Future.get()方法,加入任务还没有执行完,那么当前线程就会阻塞,直到获取到结果。
@Slf4j
public class FutureExample {
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep(5000);
return "Done";
}
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new MyCallable());
log.info("do something in main");
Thread.sleep(1000);
String result = future.get();
log.info("result:{}", result);
}
}
FutureTask类
实现了两个接口,Runnable和Future,使用起来会很方便。
@Slf4j
public class FutureTaskExample {
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep(5000);
return "Done";
}
});
new Thread(futureTask).start();
log.info("do something in main");
Thread.sleep(1000);
String result = futureTask.get();
log.info("result:{}", result);
}
}
Fork/Join框架
是java7提供的并行执行任务的一个框架,把大任务分割成若干个小任务;
参考案例:
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;
private int start;
private int end;
public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
//如果任务足够小就计算任务
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任务大于阈值,就分裂成两个子任务计算
int middle = (start + end) / 2;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
// 执行子任务
leftTask.fork();
rightTask.fork();
// 等待任务执行结束合并其结果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
// 合并子任务
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkjoinPool = new ForkJoinPool();
//生成一个计算任务,计算1+2+3+4
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
//执行一个任务
Future<Integer> result = forkjoinPool.submit(task);
try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}
BlockingQueue
主要是用于生产者消费者的情景,这个组件主要是保证线程安全的,在队列重视按照顺序先进先执行的顺序进行执行的。
-
ArrayBlockingQueue
有界的阻塞队列,内部实现是一个数组,必须要在初始化的时候指定大小。
-
DelayQueue
阻塞的是内部元素,内部元素必须实现一个接口delayed
-
LinkedBlockingQueue
可扩展大小的阻塞队列
-
PriorityBlockingQueue
有优先级的阻塞队列
-
SychronousQueue
仅容许一个元素