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

    仅容许一个元素

 posted on 2019-10-17 19:13  徐杰  阅读(221)  评论(0编辑  收藏  举报