栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

并发编程与高并发(四):JUC之AQS

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

并发编程与高并发(四):JUC之AQS

一、介绍

在JDK1.6之前,synchronized这个重量级的同步锁性能一直都是较为低下,虽然在1.6之后,进行了大量的锁优化策略,但是与Lock相比synchronized还是存在缺陷的。虽然synchronized提供了便捷性的隐式获取锁、释放锁的机制(基于JVM机制),但是它缺缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式的锁在高并发场景下性能大打折扣。

AQS是 AbstractQueuedSynchronizer 的缩写,也就是抽象队列同步器,它是JUC并发包下面的核心组件,它定义了两种资源共享模式: 

        独享式,每次只能有一个线程持有锁,例如 ReentrantLock 实现的就是独占式的锁资源。

        共享式,允许多个线程同时获取锁,并发访问共享资源,ReentrantWriteLock 和CountDownLatch 等。

它维护了一个 volatile 的 state 的变量和一个 FIFO(先进先出)的队列。

其中 state 变量代表的是竞争资源标识,而队列代表的是竞争资源失败的线程排队时存放的容器。

子类通过继承并实现它的方法管理其状态【 acquire 和 release 】的方法操纵状态。

可以同时实现排它锁和共享锁模式(独占、共享)。

FIFO队列介绍:JUC之AQS中的CLH队列 | 程序猿的博客

Condition等待队列介绍:JUC之AQS的Condition等待队列 | 程序猿的博客

二、CountDownLatch

CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。

比如有一个任务A它要等待其他四个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

CountDownLatch用了一个给定的计数器来进行初始化,对计数器的操作是原子性操作。调用await()方法的线程会一直处于阻塞状态,直到其他线程调用countDown()方法使计数器为0。

请注意,这个计数器是不可以重置的

如何使用和工作? CountDownLatch有一个构造器,用来设置计数器的值
public CountDownLatch(int count)
CountDownLatch有两个awiat()方法
public void await()
public boolean await(long timeout, TimeUnit unit)

第一个参数表示在规定时间内,如果没有执行完的话,主线程也继续执行

第二个参数表示时间单位

CountDownLatch应用场景例子

CountDownLatch可以当做一个计数器来使用,比如说某线程需要等到其他几个线程都执行过某个事件单后才能继续执行。

下面模拟一个场景,某公司一共有10个人,门卫要等待十个人都来上班后,才可以休息,代码如下

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

public class CountDownLatchTest1 {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int times = i;
            exec.execute(() -> {
                try {
                    System.out.println("子线程" + Thread.currentThread().getName() + "正在赶路");
                    Thread.sleep(1000 * times);
                    System.out.println("子线程" + Thread.currentThread().getName() + "到公司了");

                    countDownLatch.countDown();
                    System.out.println("子线程" + Thread.currentThread().getName() + "开始工作");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        exec.shutdown();
        System.out.println("门卫等待员工上班");
        countDownLatch.await();
        System.out.println("员工都来了,门卫去休息了");
    }
}

三、Semaphore

Semaphore 能控制同一时刻并发线程的数量,通过 acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。比如在 windows 下可以设 置共享文件的最大客户端访问个数。

如何使用和工作? Semaphore有两个构造器
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

第一个参数设置许可数量

第二个参数如果是true即为公平锁

获取许可的方法
public void acquire(int permits)
public boolean tryAcquire(int permits)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)

第一个参数表示获取许可的数量

第二个参数表示在规定时间尝试获取一个许可,如果获取到了执行,如果获取不到许可的话直接失败

第三个参数表示时间的单位

一个线程调用了 release() 之前并不要求一定要调用了 acquire()

如果释放的比获取的信号量还多,例如获取了2个,释放了5个,那么当前信号量就动态地增加到5了

Semaphore应用场景例子

Semaphore 实现的功能就类似于厕所有5个坑,假如有10个人要上厕所,那么同时只能有五个人占用,当5个人其中的一个让开后,等待的人中选取一个去上厕所。等待的5个人可以使随机获取机会,也可以按照顺序获取机会,这取决于构造 Semaphore 对象时传入的参数。

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

public class SemaphoreTest1 {

    private final static int threadCount = 10;

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < threadCount; i++) {
            final int threadNumber = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("thread number =" + threadNumber + " 正在上厕所");
                    Thread.sleep(2000L);
                    System.out.println("thread number = " + threadNumber + " 上完厕所了");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            });
        }
        exec.shutdown();
    }
}

四、CyclicBarrier

通过它,可以实现让一组线程等待某个状态之后再全部同时执行,CyclicBarrier可以被重用

如何使用和工作? CyclicBarrier有两个构造器
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)

第一个参数,表示那个一起执行的线程的个数

第二个参数,表示线程都处于barrier时,一起执行之前,先执行的一个线程

让线程处于barrier状态的方法await() 
public int await()
public int await(long timeout, TimeUnit unit)

第一个默认方法,表示要等到所有线程都处于barrier状态,才一起执行

第二个方法,指定了等待的时间,当所有线程没有都处于barrier状态,又到了指定的时间,所在的线程就继续执行了

CyclicBarrier应用场景例子

举一个跑步的例子

10名运动员各自准备比赛,需要等待所有运动员准备好以后,裁判才能开枪,然后跑步。代码实现如下

代码如下

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

public class CyclicBarrierTest1 {
    public static void main(String[] args) {
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(10, () -> System.out.println("裁判开枪"));
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int times = i;
            exec.execute(() -> {
                try {
                    System.out.println("子线程" + Thread.currentThread().getName() + "正在准备");
                    Thread.sleep(1000 * times);
                    System.out.println("子线程" + Thread.currentThread().getName() + "准备好了");
                    cyclicBarrier.await();
                    System.out.println("子线程" + Thread.currentThread().getName() + "跑起来了");
                } catch (Exception e) {
                    e.printStackTrace();
                }

            });
        }
        exec.shutdown();
    }
}

五、
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/305641.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号