文章目录

  • CAS、AQS、Lock、通信工具类
    • 1 CAS
      • 1.1 Unsafe类
      • 1.2 Atomic包
    • 2 AQS
    • 3 Condition
    • 4 ReentrantLock
      • 4.1 公平锁部分源码
      • 4.2 非公平锁部分源码
      • 4.3 总结
    • 5 ReentrantReadWriteLock
    • 6 StampedLock
    • 7 通信工具类
      • 7.1 Semaphore
      • 7.2 Exchanger
      • 7.3 CountDownLatch
      • 7.4 CyclicBarrier
      • 7.5 Phaser

CAS、AQS、Lock、通信工具类

1 CAS

Compare and Swap,非阻塞原子性操作

将变量当前值与原值进行比较,当变量当前值等于原值时,替换该变量为新值

CAS多于自旋组合,但自旋会耗费CPU资源。可让JVM支持处理器提供的pause指令,自旋失败时CPU睡眠一小段时间再继续自旋

无法解决ABA问题,即变量原值为A,再该线程准备修改时,另一个线程将变量改为B再改为A。可增加一个版本号标记改变次数

1.1 Unsafe类

Java通过Unsafe类下的方法实现CAS,Unsafe里面是native方法,实现是C++写的

1.2 Atomic包

Atomic包下的方法使用Unsafe实现原子操作

  • 原子更新基本类型
  • 原子更新数组
  • 原子更新引用
  • 原子更新字段

// AtomicInteger 类常用方法public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

2 AQS

AbstractQueuedSynchronizer抽象队列同步器,是一个用于构建锁和同步器的框架

AQS的核心思想是,如果被请求的资源空闲,则将当前线程设置为有效的工作线程,把资源锁定;如果被请求的资源占用,则将线程加入CLH队列中等待

CLH队列是一个虚拟双向队列,不存在队列实例,仅存在结点之间的关联,AQS将每个线程封装成一个队列的Node

资源共享模式:

  • 独占模式:一次只能一个线程获取
  • 共享模式:同时可以多个线程获取

AQS内部使用一个volatile的变量state作为资源的标识

AQS底层使用模板方法模式,自定义同步器需要重写以下方法

isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

acquire()部分源码

// arg表示获取资源的个数,独占模式下都为1
public final void acquire(int arg) {// 1. 尝试获取资源// 2. 获取资源失败则加入队列中// 3. 判断当前线程是否是第二个等待的线程,不是则阻塞if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}// 加入等待队列
private Node addWaiter(Node mode) {// 生成该线程对应的Node节点Node node = new Node(Thread.currentThread(), mode);// 将Node插入队列中Node pred = tail;if (pred != null) {node.prev = pred;// 使用CAS尝试,如果成功就返回if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 如果等待队列为空或者上述CAS失败,再自旋CAS插入enq(node);return node;
}// 自旋CAS插入等待队列
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}// 判断当前线程是否是第二个等待的线程
// 是则自旋尝试获取资源
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;// 自旋for (;;) {final Node p = node.predecessor();// 如果node的前驱结点p是head,表示node是第二个结点,就可以尝试去获取资源了if (p == head && tryAcquire(arg)) {// 拿到资源后,将head指向该结点。// 所以head所指的结点,就是当前获取到资源的那个结点或null。setHead(node); p.next = null; // help GCfailed = false;return interrupted;}// 如果自己可以休息了,就进入waiting状态,直到被unpark()if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

release()部分源码

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}private void unparkSuccessor(Node node) {// 如果状态是负数,尝试把它设置为0int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 得到头结点的后继结点head.nextNode s = node.next;// 如果这个后继结点为空或者状态大于0// 通过前面的定义我们知道,大于0只有一种可能,就是这个结点已被取消if (s == null || s.waitStatus > 0) {s = null;// 等待队列中所有还有用的结点,都向前移动for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}// 如果后继结点不为空,if (s != null)LockSupport.unpark(s.thread);
}

3 Condition

// Lock接口中有一个方法获取Condition
Condition newCondition();

Condition和Object的wait/nofity方法基本相似。Condition的await方法对应Object的wait方法,Condition的signal/signalAll方法对应Object的nofity/nofityAll方法

Condition可以实现唤醒指定的线程

public class Main {static Lock lock = new ReentrantLock();static Condition condition1 = lock.newCondition();static Condition condition2 = lock.newCondition();public static void main(String[] args) {Thread thread1 = new Thread(() -> {try {lock.lock();System.out.println("thread1 lock");condition1.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();System.out.println("thread1 unlock");}});Thread thread2 = new Thread(() -> {try {lock.lock();System.out.println("thread2 lock");
//        condition1.signalAll();condition2.signalAll();} finally {lock.unlock();System.out.println("thread2 unlock");}});thread1.start();try{Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}thread2.start();}}

4 ReentrantLock

ReentrantLock支持公平锁和非公平锁

4.1 公平锁部分源码

static final class FairSync extends Sync {final void lock() {acquire(1);}// AbstractQueuedSynchronizer.acquire(int arg)public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
}

4.2 非公平锁部分源码

static final class NonfairSync extends Sync {final void lock() {// 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}// AbstractQueuedSynchronizer.acquire(int arg)public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}
/*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 这里没有对阻塞队列进行判断if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

4.3 总结

非公平锁在调用lock后,会先进行一次CAS抢锁;如果失败了,则进入tryAcquire方法,发现锁被释放了则,则再进行一次CAS抢锁;如果失败了,则跟公平锁一样,进入阻塞队列等待唤醒

5 ReentrantReadWriteLock

支持读锁和写锁,允许多个线程同时读

6 StampedLock

StampedLock是JDK 8发布的,没有实现Lock接口和ReadWriteLock接口,但是实现了读写锁的功能,并且把读锁分为乐观读锁悲观读锁,性能比ReentrantReadWriteLock好

思想:无锁编程思想,与CAS自旋思想一样,在读的时候如果发生了写,则应该通过重试的方式获取新值,不应该阻塞写操作

具体地:在获取乐观读锁后,读操作后,调用validate方法检查期间是否有过写操作,有的话则获取悲观读锁,重新读

7 通信工具类

作用
Semaphore 信号量,限制线程的数量
Exchanger 两个线程交换数据
CountDownLatch 线程等待直到计数器减为0开始工作
CyclicBarrier 多个线程到达某处阻塞,全部到达后再继续工作
Phaser 增强的CyclicBarrier

7.1 Semaphore

信号量,支持公平锁和非公平锁,可设置信号量为1个或者多个

每次申请信号量,值减1,值为0时申请不到,线程阻塞

实现原理:继承AQS,获取资源失败则进入等待队列

semaphore.acquire();
semaphore.release();

7.2 Exchanger

用于两个线程交换数据

线程调用exchange后会阻塞,直到另一个线程调用了exchange,才会继续执行

Exchanger<String> exchanger = new Exchanger<>();new Thread(() -> {try {exchanger.exchange("这是来自线程A的数据"));} catch (InterruptedException e) {e.printStackTrace();}
}).start();

7.3 CountDownLatch

等待多个线程完成某一件事,阻塞的线程才能继续执行

具体地:初始化设置一个值,每个线程调用countDown将值减1,调用await的线程会阻塞,直到值减为0才继续运行

缺点:count值只能初始化的时候设置一次,无法重新设置

// 构造方法:
public CountDownLatch(int count)public void await() // 等待
public boolean await(long timeout, TimeUnit unit) // 超时等待
public void countDown() // count - 1
public long getCount() // 获取当前还有多少count

7.4 CyclicBarrier

多个线程相互等待对方完成某一件事,全部线程完成后,这些线程才能继续执行

具体地:初始化设置一个值,每个线程调用await会阻塞,直到调用await阻塞的线程数目达到这个值,屏障打开,所有线程继续工作

CyclicBarrier可以通过rest重置屏障

当线程在等待过程中,屏障被破坏,会抛出异常,通过isBroken检查

  • 已有线程处于等待,调用reset方法,抛出BrokenBarrierException
  • 等待的线程被中断,抛出BrokenBarrierException,并传播到所有线程
  • 执行屏障操作过程中发生异常,异常传播到其他线程,其他线程抛出BrokenBarrierException
  • 超出指定指定等待时间,当前线程抛出TimeoutException,其他线程抛出BrokenBarrierException

7.5 Phaser

与CyclicBarrier的不同之处在于

在使用过程中可以动态增减屏障阻挡得数量

Java并发(六)——CAS、AQS、Lock、通信工具类相关推荐

  1. 常用并发工具类(锁和线程间通信工具类)

    常用并发工具类总结 JUC 下的常用并发工具类(锁和线程间通信工具类),主要包括 ReentrantLock.ReentrantReadWriteLock.CountDownLatch.CyclicB ...

  2. Java并发基石CAS原理以及ABA问题

    在学习CAS之前,先从一个简单的案例入手,进而引出CAS的基本使用: 1.基于CAS的网站计数器 需求: 我们开发一个网站,需要对访问量进行统计,用户每发送一次请求,访问量+1,如何实现? 我们模拟有 ...

  3. java 并发开发之AQS

    java 并发开发之AQS AQS 是什么,有什么作用? ① 是什么:AQS 是抽象队列同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖它 ② 有什么作用:为Java的并发 ...

  4. java 手机号脱敏,身份证号脱敏 工具类

    java 手机号脱敏,身份证号脱敏 工具类 import org.apache.commons.lang3.StringUtils;/*** * @title: 脱敏工具类* @author: wll ...

  5. java将链接生成二维码工具类

    一.添加依赖 <!-- 生成二维码--><dependency><groupId>com.google.zxing</groupId><artif ...

  6. Java - HuTool 使用 EscapeUtil、XmlUtil等工具类(四)

    Java - HuTool 使用 EscapeUtil.XmlUtil等工具类(四) 本篇主要介绍 HuTool工具, 其是 java工具类,对于一些静态方法进行封装,虽然很小,但很全,里面拥有平时我 ...

  7. Java生成和解析二维码工具类(简单经典)

    Java生成和解析二维码工具类 开箱即用,简单不废话. pom.xml引入依赖 <!-- https://mvnrepository.com/artifact/com.google.zxing/ ...

  8. 记录一下:Java 汉字获取拼音或首字母工具类

    记录一下:Java 汉字获取拼音或首字母工具类 Maven依赖配置 Java代码 本文主要记录一下在Java中,如何将字符串中的中文转化为拼音,获取汉字串拼音首字母,获取汉字串拼音的工具类,以及相关的 ...

  9. Java教程:微信排序并加密工具类

    Java教程:微信排序并加密工具类 源码: import cn.bsit.commons.md5.MD5Utils;import java.util.Arrays; import java.util. ...

最新文章

  1. 小tips:JS中typeof与instanceof用法
  2. eclipse总跳出password required的框解决办法
  3. mysql专区_MySQL-技术专区-详解索引原理
  4. [BUUCTF-pwn]——wustctf2020_closed
  5. python安装mysqlclient_Python-安装mysqlclient(MySQLdb)
  6. 情态 语态_情绪与情态与对话情感
  7. TVS管、稳压管、肖特基二极管
  8. Mysql5.7与8.0版本不兼容问题
  9. 洛谷P4568飞行路线(分层图最短路)
  10. 力扣36.有效的数独
  11. java 汽车加油问题_贪心算法---汽车加油问题
  12. 判断四个点是否可以构成矩形(优雅的解法!!!)
  13. 中级php工程师笔试,PHP工程师笔试题目及行测题型示例
  14. 5G 技术特点与应用
  15. 回归中的相关度和R平方值 学习笔记
  16. 【STM32】NRF24L01模块的收发调试
  17. 【C语言】学籍管理系统
  18. Spring Boot使用@RepeatSubmit 防止重复提交
  19. c++如何画实心的箭头
  20. “火星人”马斯克推论:世界很大可能性是被编程的,上帝可能是个程序员!

热门文章

  1. cocos2dx怎样设置ios和Android横屏竖屏的几种方法
  2. php用魔术方法__call实现类函数重载
  3. 防止浏览器拦截的新窗口打开链接方案
  4. HttpComponents分析之连接池实现
  5. jar包上传到jcenter
  6. JS组件系列——又一款MVVM组件:Vue(一:30分钟搞定前端增删改查)
  7. bankbone 模型简单介绍
  8. [转] Mou 一个Markdown工具 语法规则文档(最后)
  9. Apache下的配置文件httpd.conf、httpd-vhosts.conf 转
  10. 敏感词过滤,并实现替换