【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用

一、整体介绍

这章很重要,相对其他同步组件难,但是也很重要。

AQS的数据结构

【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第1张图片

AQS的设计

【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第2张图片
【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第3张图片

AQS实现的大致思路

AQS同步组件

【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第4张图片
【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第5张图片

二、各个组件具体的使用示例

*countdownlatch使用场景

【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第6张图片

countdownlatch使用场景

  1. 并行计算

countdownlatch使代码演示

package com.mmall.concurrency.AQS;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class countdownlatchDemo {
    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();
        log.info("finish");
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
        Thread.sleep(100);
    }
}

使用场景2,指定时间,超时不管,执行多少算多少,防止代码死卡

          // 只等待 test2 方法30 毫秒
        countDownLatch.await(30, TimeUnit.MILLISECONDS);

countDownLatch使用总结

  1. final CountDownLatch countDownLatch = new CountDownLatch(Count);定义一个计数器
  2. countDownLatch.countDown();
  3. countDownLatch.await();
  4. exec.shutdown();

二、Semaphore介绍

它用于仅能提供有限访问的资源,比如数据库连接输入最大20,做并发访问控制
代码如下,

import java.util.concurrent.*;

@Slf4j
public class SemaphoreDemo1 {
    private final static int threadCount = 50;

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();

        //  final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        //  定义一个信号量,每次允许并发20个
        final Semaphore semaphore = new Semaphore(20);
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire();
                    test3(threadNum);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }
    private static void test3(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
    }
}

但是有时候我们不一定一次拿到一个许可,而是多个。来看看多个的情况。

                    semaphore.acquire(5);
                    test3(threadNum);
                    semaphore.release(5);

但是还有一种情况,有时候并发实在太高了,我们想少点,多了之后就丢弃。重点看tryAcquire() 这个方法。代超时时间的参数。

                    if (semaphore.tryAcquire(1000,TimeUnit.MILLISECONDS)){
                        test3(threadNum);
                        semaphore.release();
                    }

总结

获取一个许可
获取多个许可
尝试获取一个许可
尝试等待获取一个许可
尝试等待获取多个许可

三、 CylicBarrier

【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第7张图片
countdownlatch 和 CylicBarrier相似:

  1. 前者计数器只能使用一次,而后者可以通过set重复使用
  2. 前者是一个或者多个线程要等其他线程都执行完后一起执行,一个或者多个线程要等待其他线程关系
    而后者描述各个进程相互等待的关系。更强大
package com.mmall.concurrency.AQS;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CycliBarrierDemo {
    private static int threadNum;
    // 等待的线程数够5个就执行
    static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    public static void main(String[] args) throws Exception {
        //线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i< 10; i++){
            final int threadNum = i;
            Thread.sleep(1000); //执行前睡一会
            executorService.execute( () -> {
                try{
                    race(threadNum);
                }catch (Exception e){
                    log.error("race error :",e);
                }
            });
        }
    }

    public static void race(int threadNum) throws Exception{
        Thread.sleep(1000);
        log.info("{} is ready.", threadNum);
        cyclicBarrier.await();
        log.info("{} is continue.", threadNum);
    }
}

awite()还有等待时

cyclicBarrier.await(1000, TimeUnit.MILLISECONDS); 会报错

报错时,如果还想执行下面的语句,那就try{} catch(){}

19:06:29.114 [pool-1-thread-1] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 0 is ready.
19:06:30.112 [pool-1-thread-2] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 1 is ready.
19:06:30.189 [pool-1-thread-1] ERROR com.mmall.concurrency.AQS.CycliBarrierDemo - race error :
java.util.concurrent.TimeoutException: null
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.mmall.concurrency.AQS.CycliBarrierDemo.race(CycliBarrierDemo.java:38)

//  改造之后
19:09:57.986 [pool-1-thread-1] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 0 is ready.
19:09:58.981 [pool-1-thread-2] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 1 is ready.
19:09:58.998 [pool-1-thread-1] WARN com.mmall.concurrency.AQS.CycliBarrierDemo - ===============出错?别出来
19:09:58.998 [pool-1-thread-2] WARN com.mmall.concurrency.AQS.CycliBarrierDemo - ===============出错?别出来
19:09:58.998 [pool-1-thread-1] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 0 is continue.
19:09:58.998 [pool-1-thread-2] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 1 is continue.

static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);这个函数里还可以有优先执行的回调函数。

    // 等待的线程数够5个就执行
    static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
            () ->{log.info("====running"); });

运行结果:

19:13:26.624 [pool-1-thread-5] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 4 is ready.
19:13:26.624 [pool-1-thread-5] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - ====running
19:13:26.624 [pool-1-thread-5] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 4 is continue.
19:13:26.625 [pool-1-thread-1] INFO com.mmall.concurrency.AQS.CycliBarrierDemo - 0 is continue.

四、ReentrantLock(可重入锁)和synchronized区别

reentrantlock 阻止锁在用户级别处理,而不进入内核阻塞,用CAS原理
【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第8张图片
【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第9张图片

代码演示功能

    // 定义锁
    private static Lock lock = new ReentrantLock();
==============================
    private static void add(){
        lock.lock();
        try{
            count++;
        }catch (Exception e){
            log.error("{} add 报错", e.getMessage());
        }finally {
            lock.unlock();
        }
    }

再深入看reentrantlock源码做了什么?

【 高并发9】AQS-AbstractQueuedSynchronizer的详解及其同步组件的使用_第10张图片

接下来再看看juc的另外一个锁ReentrantReadWriteLock

ReentrantReadWriteLock:在没有任何读写锁的时候才可以取得写入锁,读取锁定。

package com.mmall.concurrency.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Slf4j
public class ReadWriteLock {
    private final Map 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 getAllKeys(){
        readLock.lock();
        try{
            return map.keySet();
        }finally {
            readLock.unlock();
        }
    }

    // put func
    public Data put(String key, Data value){
        writeLock.lock();
        try{
            return map.put(key,value);
        }finally {
            readLock.unlock();
        }
    }

    // Data 方法
    class Data{

    }
}

再来介绍一种锁StampedLock

主要分析悲观乐观的区别

package com.mmall.concurrency.lock;

import java.util.concurrent.locks.StampedLock;

public class StampLockDemo {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }

        //下面看看乐观读锁案例
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
            double currentX = x, currentY = y;  //将两个字段读入本地局部变量
            if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
                stamp = sl.readLock();  //如果没有,我们再次获得一个读悲观锁
                try {
                    currentX = x; // 将两个字段读入本地局部变量
                    currentY = y; // 将两个字段读入本地局部变量
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        //下面是悲观读锁案例
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
                    long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
                    if (ws != 0L) { //这是确认转为写锁是否成功
                        stamp = ws; //如果成功 替换票据
                        x = newX; //进行状态改变
                        y = newY;  //进行状态改变
                        break;
                    } else { //如果不能成功转换为写锁
                        sl.unlockRead(stamp);  //我们显式释放读锁
                        stamp = sl.writeLock();  //显式直接进行写锁 然后再通过循环再试
                    }
                }
            } finally {
                sl.unlock(stamp); //释放读锁或写锁
            }
        }
    }
}

stampedLock 的用法

  // 定义锁
    private static StampedLock stampedLock = new StampedLock();
    =========
    private static void add1(){
        long stamp = stampedLock.writeLock();
        try{
            count++;
        }catch (Exception e){
            log.error("{} add 报错", e.getMessage());
        }finally {
            stampedLock.unlock(stamp);
        }
    }

总结各种锁

  1. synchronized实在jvm层面实现,jvm自动解锁,其他一定要自己解锁。
  2. reentrantlock , reentrantreadlock, reentrantwritelock, stampedlock,都是在对象层面锁定,unlock放在finally更安全。
  3. stampedlock,改进很大, 尤其在读很大的时候,容易吴用

关于condition的使用

多线程间协调通信工具类,条件具备,等待进程唤醒。
很少用,面试知道即可

package com.mmall.concurrency.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ConditionLock {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {
            try {
                reentrantLock.lock();
                log.info("wait signal"); // 1
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get signal"); // 4
            reentrantLock.unlock();
        }).start();

        new Thread(() -> {
            reentrantLock.lock();
            log.info("get lock"); // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll();
            log.info("send signal ~ "); // 3
            reentrantLock.unlock();
        }).start();
    }
}

你可能感兴趣的:(Java并发编程及并发面试点)