AQS同步组件
- CountDownLatch
维护一个计数器State,每调用countdown计数器减1,当调用await时,会将当前线程放到阻塞队列中并挂起,只有当state为0时,才会唤醒阻塞队列中的线程(也就是移到同步队列),该线程才会执行。
当某个计算需要等待另一个应用的完成时使用,用法:
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(20);
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
service.execute(() -> {
test1(threadNum);
// 在子线程完成后 countDown
latch.countDown();
});
}
latch.await(); // 会一直等待,只有当上面20个线程执行完成后,test2才会被执行
// latch.await(20,TimeUnit.SECONDS); 可以设置超时时间
test2();
}
- Semaphore
控制同时访问的个数,用于有限资源的访问,比如数据库
private static void test3(ExecutorService service) {
Semaphore semaphore = new Semaphore(20);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
service.execute(() -> {
try {
semaphore.tryAcquire();// 尝试获取许可
// semaphore.tryAcquire(2);//尝试获取3个许可
// semaphore.tryAcquire(1, TimeUnit.SECONDS);// 尝试获取许可,试图等待1s
// semaphore.acquire(); 获取许可
// semaphore.acquire(3);// 获取许可 可以获取一个,也可以获取多个
test1(threadNum);
semaphore.release(3);// 释放许可 ,可以释放一个或多个
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
- CyclicBarrier
栅栏:允许一组线程相互等待,当各自都准备就绪后,才各自向下继续执行,如赛跑。线程调用await进入等待,计数器减一,当计数器为0时,则会被唤醒。线程释放后计数器可以循环使用
与CountDownLatch很相似,都是通过计数器实现 区别:
CountDownLatch的计数是递减的,并且是单次使用;
static CyclicBarrier cyclicBarrier = new CyclicBarrier(waitSize);
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < threadSize; i++) {
Thread.sleep(1000);
final int threadNum = i;
service.execute(()->{
try {
test1(threadNum);
} catch (Exception e) {
e.printStackTrace();
}
});
}
service.shutdown();
}
private static void test1(int threadNum) {
System.out.println(threadNum + " 线程 准备就绪 。。。");
try {
Thread.sleep(1000);
cyclicBarrier.await();
// 定时
// cyclicBarrier.await(10,TimeUnit.SECONDS);
} catch (Exception e) {
}
System.out.println(threadNum + " 线程 发车 。。。");
}
// 可以指定一个 runrable,它会优先执行
static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(threadSize,()->{
System.out.println("先执行");
});
- ReentrantLook
实现:是一种自旋锁,通过循环调用cas来实现加锁,性能比较好也是因为避免了使线程进入阻塞态。
使用:
/**
* 声明锁的实例
*/
static ReentrantLock reentrantLock = new ReentrantLock();
// 设置为公平锁
//static ReentrantLock reentrantLock = new ReentrantLock(true);
private static void test1() {
// 加锁
reentrantLock.lock();
try {
// 在调用时,只有它是空闲的,才能获取锁
reentrantLock.tryLock();
// 在给定时间内,它是空闲的或者没有别其他线程所中断,则获取
reentrantLock.tryLock(100, TimeUnit.SECONDS);
//如果当前线程没有被中断,则获取锁定
reentrantLock.lockInterruptibly();
// 查询此锁是否由任何线程持有
reentrantLock.isLocked();
//查询当前线程是否持有此锁定。
reentrantLock.isHeldByCurrentThread();
// 是否是公平锁
reentrantLock.isFair();
//其他方法不再一一举例
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
reentrantLock.unlock();
}
}
- ReentrantReadWriteLock
适用于读多写少的场景,没有任何读锁的时候才能获取写锁,悲观读取(当读取过多时,写可能遭遇饥饿)
用例:
public class LockExample2 {
private final Map<String, String> map = new TreeMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public String get(String key){
readLock.lock();
try {
return map.get(key);
}finally {
readLock.unlock();
}
}
public void put(String key,String value){
writeLock.lock();
try {
map.put(key,value);
}finally {
writeLock.unlock();
}
}
}
- StampedLock
支持写、读和乐观读
状态由版本和模式两部分组成,锁获取方法返回一个数字作为票据,释放锁时,需要传入该值
BlockingQueue阻塞队列
BlockingQueue 会让服务线程在队列为空时,进行等待,当有新的消息进入队列后,自动唤醒线程。提供四套使用方法
1. ArrayBlockingQueue:
初始化时需要指定大小,并且不能改变,先进先出原则
2. LinkedBlockingQueue:
大小可选,如果指定就是固定的,不指定就是不固定的,先进先出
3. DelayQueqe:
元素需要实现delayt接口,有序的,一般按元素过期时间的优先级排序,由于定时关闭连接,超时处理等
4. PriorityBlockingQueue:
带优先级的阻塞队列,没有边界,有排序规则,允许插入null,插入的对象必须实现 compable 接口
5. SynchronousQueue:
仅允许容纳一个元素,同步队列