Java高并发--AQS

主要是学习慕课网实战视频《Java并发编程入门与高并发面试》的笔记

AQS是AbstractQueuedSynchronizer的简称,直译过来是抽象队列同步器。AQS的底层数据结构是队列,如下所示

AQS使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架,利用一个int类型表示状态(state)

使用该框架的功能需要让子类继承,并重写相关方法。

  • 子类通过继承并通过重写方法管理其状态acquire和release的方法操纵状态

  • 可以同时实现排他锁和共享锁模式(独占、共享),要么使用独占锁要么使用共享锁,而不会同时使用两者

闭锁- CountdownLatch

常称为"闭锁",是一个倒计数器,如下,设定了倒计数值为3.当前线程调用await()被阻塞,其他线程每调用一次countDown()计数器减1,一直到计数值cnt等于0时,当前线程才可以继续(恢复)执行。可以理解为一个线程等待多个线程执行完毕,这样该线程可以利用其他多个线程的执行结果。

下面是CountdownLatch的简单使用,当计数器从60减到0时候,当前线程会打印“Go!”

由于当前线程被阻塞,需等到其余60个线程执行完毕,因此当前线程的"Go!"会最后打印。

package com.shy.concurrency.aqs;

import lombok.extern.slf4j.Slf4j;

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

/**
 * @author Haiyu
 * @date 2019/1/3 11:00
 */
@Slf4j
public class CountdownLatchExample {
    private static CountDownLatch cdl = new CountDownLatch(60);

    public static void main(String[] args) throws InterruptedException {
        final int num = 60;
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = num; i >= 0; i--) {
            final int cur = i;
            executorService.execute(() -> {
                try {
                    run(cur);
                } catch (InterruptedException e) {
                    log.error("exception", e);
                } finally {
                    cdl.countDown();
                }
            });
        }
        cdl.await();
        System.out.println("Go!");
        executorService.shutdown();
    }

    public static void run(int cur) throws InterruptedException {
        Thread.sleep(100);
        System.out.println(cur);
    }
}

使用CountdownLatch还可以实现限时任务,在await()方法中可以传入参数,如下表示当前线程只会等待10毫秒,超过这个时间就不再等待而是恢复执行。

cdl.await(10, TimeUnit.MICROSECONDS);

将上面的程序cdl.await();修改成cdl.await(10, TimeUnit.MICROSECONDS);,将最先打印"Go!"因为它只等待10毫秒,而其他线程每次执行前都会sleep100毫秒。

信号量 - Semaphore

可以控制某个资源可以有多少个线程同时访问,即可以控制并发量。主要通过acquire和release方法,

  • acquire():获得一个许可,若无法获得会一直等待直到有线程释放了许可或者被中断
  • tryAcquire():尝试获得一个许可,获取成功返回true,失败返回false,不会等待立即返回
  • release():释放一个许可

acquire()和release()还可以传入参数,指定一次获得/释放的许可数;tryAcquire()还可以指定时间,在指定时间内没有获得到许可就返回false。

循环栅栏 - CyclicBarrier

cyclicBarrier强调线程之间互相等待

CountdownLatch和CyclicBarrier的区别如下:

  • CountDownLatch强调一个线程等待其他所有线程,通过cdl.await()让当前线程等待在倒计数器上,每有一个线程执行完,cdl.countDown(),将计数减1,减到0时通知当前线程执行。简单的说就是一个线程等待,直到他所等待的其他线程都执行完成,当前线程才可以继续执行。
  • cyclicBarrier强调线程之间互相等待,只要有一个线程还没到来,所有线程会一起等待。可以传入一个Runnable作为计数完成要执行的任务。每有一个线程调用cyc.await()计数减1,减到0时会执行一次该Runnable。简单地说就是线程之间互相等待,等所有线程都准备好,即调用await()方法之后,执行一次Runnable,此时所有线程开始同时执行!
  • CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。

下面是一个例子,CyclicBarrier还可以传入一个Runnable, 每次计数到的时候都会执行一次。

package com.shy.concurrency.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

/**
 * @author Haiyu
 * @date 2019/1/3 11:00
 */
@Slf4j
public class CyclicBarrierExample {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(10, () -> System.out.println("集合完毕"));

    public static void main(String[] args) throws InterruptedException {
        final int num = 10;
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0;i < num; i++) {

            final int cur = i;
            executorService.execute(() -> {
                try {
                    run(cur);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executorService.shutdown();
    }

    public static void run(int cur) throws InterruptedException, BrokenBarrierException {
        log.info("{} is ready", cur);
        cyclicBarrier.await();
        log.info("{} is working", cur);
    }
}

CyclicBarrier(10, () -> System.out.println("集合完毕"));该句表示10个线程相互等待,所有10个线程都调用了await()方法后,会执行一次传入的Runnable;之后10个线程被唤醒,计数重新开始,这也是Cyclic的意义——可重复利用的计数器。

因此上面程序会先打印10个ready,然后打印一次“集合完毕”;之后会打印10个working。

重入锁 - ReentrantLock

synchronized是JVM的内置锁,而重入锁是Java代码实现的。重入锁是synchronized的扩展,可以完全代替后者。重入锁可以重入,允许同一个线程连续多次获得同一把锁。其次,重入锁独有的功能有:

  • 可以相应中断,synchronized要么获得锁执行,要么保持等待。而重入锁可以响应中断,使得线程在迟迟得不到锁的情况下,可以不再等待。主要由lockInterruptibly()实现,这是一个可以对中断进行响应的锁申请动作,锁中断可以避免死锁。
  • 锁的申请可以有等待时限,用tryLock()可以实现限时等待,如果超时还未获得锁会返回false,也防止了线程迟迟得不到锁时一直等待,可避免死锁。
  • 公平锁,即锁的获得按照线程先来后到的顺序依次获得,不会产生饥饿现象。synchronized的锁默认是不公平的,重入锁可通过传入构造方法的参数实现公平锁。
  • 重入锁可以绑定多个Condition条件,这些condition通过调用await/singal实现线程间通信。可以实现分组唤醒需要唤醒的线程,而不是像synchronized的wait/notify一样要么随机唤醒要给要么唤醒全部。

读写锁 - ReentrantReadWriteLock

ReadWriteLock即读写锁,它有两个方法如下,分别返回一个读锁和写锁,即读写锁分离。

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();

在读时使用readLock进行加锁,在写时使用writeLock进行加锁。使得读-读不阻塞,读线程完全并行,适合读多写少的场合。读锁会完全阻塞写锁,它使用的依然是悲观的锁策略。如果有大量的读线程,也有可能引起写线程的饥饿。(如果想获得写锁,不允许还有读锁、写锁还保持着,即 在没有任何读锁、写锁的情况下才能获得写锁)

Java高并发--AQS的更多相关文章

  1. Java高并发 -- J.U.C.组件扩展

    Java高并发 -- J.U.C.组件扩展 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 FutureTask Future模式,核心思想是异步调用.和同步调用的区别 ...

  2. Java高并发综合

    这篇文章是研一刚入学时写的,今天整理草稿时才被我挖出来.当时混混沌沌的面试,记下来了一些并发的面试问题,很多还没有回答.到现在也学习了不少并发的知识,回过头来看这些问题和当时整理的答案,漏洞百出又十分 ...

  3. java高并发系列 - 第22天:java中底层工具类Unsafe,高手必须要了解

    这是java高并发系列第22篇文章,文章基于jdk1.8环境. 本文主要内容 基本介绍. 通过反射获取Unsafe实例 Unsafe中的CAS操作 Unsafe中原子操作相关方法介绍 Unsafe中线 ...

  4. Java高并发系列——检视阅读

    Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...

  5. Java高并发编程基础三大利器之CountDownLatch

    引言 上一篇文章我们介绍了AQS的信号量Semaphore<Java高并发编程基础三大利器之Semaphore>,接下来应该轮到CountDownLatch了. 什么是CountDownL ...

  6. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  7. 【实战Java高并发程序设计 7】让线程之间互相帮助--SynchronousQueue的实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  8. 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  9. 【实战Java高并发程序设计 5】让普通变量也享受原子操作

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

随机推荐

  1. 1.1.5 PROB Friday the Thirteenth

    Friday the Thirteenth Is Friday the 13th really an unusual event? That is, does the 13th of the mont ...

  2. ETC(电子不停车收费系统)的发展演变

    ETC引进中国是在上世纪的90年代中期,当时中国部分经济发达地区的高速公路车流量激增,从而导致了收费口的交通堵塞.高速公路堵车现象时有发生,拥堵严重的路段可能会天天堵,有时候一堵好几天.高速公路管理手 ...

  3. email program (客户端)演变过程有感

    以下内容全部为个人读后感(参考百度百科的相关资料) 首先我认为电子邮件是一个非常伟大的发明,它不仅成本低,而且传输效率快! 关于它的起源,我从百度百科中看到了两种说法     1.1969年10月世界 ...

  4. spark-submit(spark版本2.4.2)

    spark-submit官方文档 :http://spark.apache.org/docs/latest/submitting-applications.html Launching Applica ...

  5. VMware虚拟机磁盘操作占用过高问题

    使用虚拟机运行Linux图形桌面时,经常因为一个网页或者编译某个程序就导致虚拟机卡死,甚至影响主机使用.明明主机内存有8G,分配给虚拟机的内存也不少,为什么就这么卡顿.打开主机上的任务管理器查看发现磁 ...

  6. IntelliJ IDEA 使用前常用设置

    0.设置位置 以下设置基于IntelliJ IDEA 2018.3.2 版本. IDEA 的设置一般都在 File 下的 Settings... 里进行设置的. 1.设置字体字号行间距 2.设置背景图 ...

  7. Kali学习笔记40:SQL手工注入(2)

    上一篇讲到可以通过注入得到数据库中所有的表信息 而SQL注入能不能做数据库之外的事情呢? 读取文件: ' union select null,load_file('/etc/passwd') -- 为 ...

  8. JavaScript 数组方法

    数组方法: 1.Array.join([param]) 方法:将数组中所有的元素都转换为字符串并连接起来,通过字符 param 连接,默认使用逗号,返回最后生成的字符串 2.Array.reverse ...

  9. 记一次 net 使用 data.oracleclient 使用错误OCIEnvCreate 失败, 返回代码为-1

    前提: 公司除了领导和开发人员具有管理员权限,其他人员使用的都是域账号. 过程: 应要求开发一个 winfrom项目,使用data.oracleclient  本地开发,调试无误,放到服务器共享域用户 ...

  10. 使用 WRK 压力测试工具对 ASP.NET Core 的接口进行压力测试

    0. 简要介绍 WRK 是一款轻量且易用的 HTTP 压力测试工具,通过该工具我们可以方便地对我们所开发的 WebAPI 项目进行压力测试,并且针对测试的情况返回结果. PS:Wrk 并不能针对测试的 ...