深入分析Java并发编程之CAS

在Java并发编程的世界里,synchronized 和 Lock 是控制多线程并发环境下对共享资源同步访问的两大手段。其中 Lock 是 JDK 层面的锁机制,是轻量级锁,底层使用大量的自旋+CAS操作实现的。

学习并发推荐《Java并发编程的艺术》

那什么是CAS呢?CAS,compare and swap,即比较并交换,什么是比较并交换呢?在Lock锁的理念中,采用的是一种乐观锁的形式,即多线程去修改共享资源时,不是在修改之前就加锁,而是乐观的认为没有别的线程和自己争锁,就是通过CAS的理念去保障共享资源的安全性的。CAS的基本思想是,拿变量的原值和内存中的值进行比较,如果相同,则原值没有被修改过,那么就将原值修改为新值,这两步是原子的,能够保证同一时间只有一个线程修改成功。这就是CAS的理念。

Java中要想使用CAS原子的修改某值,怎么做呢?幸运的是Java提供了这样的API,就是在sun.misc.Unsafe.java类中。Unsafe,中文名不安全的,也被称为魔术类,魔法类。

Unsafe类介绍

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,一旦能够直接操作内存,这也就意味着

(1)不受JVM管理,意思就是使用Unsafe操作内存无法被JVM GC,需要我们手动GC,稍有不慎就会出现内存泄漏。
(2)Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,并且偏移量要自己计算(其提供的有计算偏移量的方法),所以一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。
(3)直接操作内存,所以速度更快,在高并发的条件之下能够很好地提高效率。

因此,从上面三个角度来看,虽然在一定程度上提升了效率但是也带来了指针的不安全性。这也是它被取名为Unsafe的原因吧。

下面我们深入到源码中看看,提供了什么方法直接操作内存。

打开Unsafe这个类,我们会发现里面有大量的被native关键字修饰的方法,这意味着这些方法是C语言提供的实现,底层调的是C语言的库函数,我们无法直接看到他的源码实现,需要去从OpenJDK去看了。另外还有一些基于native方法封装的其他方法,整个Unsafe中的方法大致可以归结为以下几类:

(1)初始化操作
(2)操作对象属性
(3)操作数组元素
(4)线程挂起和恢复
(5)CAS机制

CAS的使用

如果你学过java并发编程的话,稍微阅读过JUC并发包里面的源码的话,对这个Unsafe类一定不陌生,因为整个java并发包底层实现的核心就是靠它。JUC并发包中主要使用它提供的CAS(compare and swap,比较并交换)操作,原子的修改锁的状态和一些队列元素。

没看过JUC源码的读者也不用担心,今天我们就是简单介绍Unsafe类中的CAS操作,那么我们接下来就会通过一个简单的例子来看看Unsafe的CAS是怎么使用的。

首先,使用这个类我们第一个要做的事情就是拿到这个类的实例,下面我们自定义了一个Util类用来获取Unsafe的实例

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeUtil {
 public static Unsafe reflectGetUnsafe() {
  try {
   Field field = Unsafe.class.getDeclaredField("theUnsafe");
   field.setAccessible(true);
   return (Unsafe) field.get(null);
  } catch (Exception e) {
   e.printStackTrace();
  }
  return null;
 }
}

这个工具类通过反射的方式拿到Unsafe类中的一个名为theUnsafe字段,该字段是Unsafe类型,并在static块中new一个Unsafe对象初始化这个字段(单例模式)。

然后我们定义了一个AtomicState类,这个类很简单,有一个int型的state字段,还有一个Unsafe的常量,以及int型的offsetState,用来记录state字段在AtomicState对象中的偏移量。具体代码如下:

import com.walking.juc.util.UnsafeUtil;
import sun.misc.Unsafe;
public class AtomicState {
 private volatile int state = 0;
 public int getState() {
  return state;
 }

 private static final Unsafe UNSAFE = UnsafeUtil.reflectGetUnsafe();
 private static final long offsetState;
 static {
  try {
   offsetState = UNSAFE.objectFieldOffset(AtomicState.class.getDeclaredField("state"));
  } catch (NoSuchFieldException e) {
   throw new Error(e);
  }
 }
 public final boolean compareAndSetState(int oldVal, int newVal) {
  return UNSAFE.compareAndSwapInt(this, offsetState, oldVal, newVal);
 }
}

我们定义了一个compareAndSetState方法,需要传两个参数,分别是state的旧值和新值,也就是读到的state的之前的值,以及想要把它修改成什么值,该方法内部调用的是Unsafe类的compareAndSwapInt方法,它有四个参数,分别是要修改的类实例对象、要修改的值的偏移量、旧值、新值。解释一下偏移量,刚才我们提到Unsafe提供给我们直接访问内存的能力,那么访问内存肯定是要知道内存的地址在哪才能去修改其相应的值吧,我们看,第一个参数是对象实例引用,也就是说,已经知道这个对象的地址了,那么我们想修改这个对象里的state的值,就只需要计算出state在这个对象的偏移量就能找到state所在的内存地址,那就可以修改它了。

然后,我们通过一个测试类来验证Unsafe的CAS操作。这个测试类我来解释下大致的思想,我们弄5个线程,让这个5个线程一个个启动,我们无法保证线程同时开始启动,那么我们有办法保证这个5个线程同时执行我们的代码,就是使用JUC包里的CyclicBarrier工具来实现的,这个工具初始化时需要传入一个int值n,我们在线程的run方法内部在业务代码执行之前调用CyclicBarrier的await方法,当指定数量n的线程都调用了这个方法那么这n个线程将同时往下执行,就像设置了一个屏障,所有人都达到这个屏障后,一起通过屏障,依次来模拟多线程并发

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
@Slf4j
public class TestAtomicState {

 static int tNum = 5;//线程数 我们开10个线程模拟多线程并发
 static CyclicBarrier cyclicBarrier = new CyclicBarrier(tNum);//栅栏
 static CountDownLatch countDownLatch = new CountDownLatch(tNum);//计数器
 static AtomicState atomicState = new AtomicState();

 public static void main(String[] args) throws InterruptedException {
  for (int i = 1; i <= tNum; i++) {
   new Thread(new MyTask(),"t-"+i).start();
  }
  countDownLatch.await();//为的是让主线程在这句阻塞住,等待所有线程执行完毕(计数器减到0)再往下走
  log.info("state最后的值:" + atomicState.getState());
 }

 static class MyTask implements Runnable{
  @Override
  public void run() {
   try {
    log.info(Thread.currentThread().getName() + "到达起跑线");
    String name = Thread.currentThread().getName();
    String substring = name.substring(name.indexOf("-") + 1);
    int i1 = Integer.parseInt(substring);

    cyclicBarrier.await();//设置一个屏障,所有线程达到这后开始一起往下执行 模拟并发
    boolean b = atomicState.compareAndSetState(0, i1);
    if (b) {
     log.info("修改成功,tName:{}" ,Thread.currentThread().getName());
    } else {
     log.info("修改失败,tName:{}" ,Thread.currentThread().getName());
    }
   } catch (InterruptedException | BrokenBarrierException e) {
    e.printStackTrace();
   } finally {
    countDownLatch.countDown();//线程执行完毕计数器减一
   }
  }
 }
}

在cyclicBarrier.await();之后我们调用AtomicState的compareAndSetState方法传入旧值0和新值,新值就是线程名t-n中的n,哪个线程修改成功,最后state值就是线程名中的数字。
至于CountDownLatch使用它的目的是让mian线程等到t-1到t-5的线程全部执行完后打印state的值。我们的重点不是CyclicBarrier和CountDownLatch,知道它们是干什么的就行。

然后我们运行这个测试程序:

13:57:46.619 [t-2] INFO com.walking.castest.TestAtomicState - t-2到达起跑线
13:57:46.619 [t-3] INFO com.walking.castest.TestAtomicState - t-3到达起跑线
13:57:46.619 [t-5] INFO com.walking.castest.TestAtomicState - t-5到达起跑线
13:57:46.619 [t-1] INFO com.walking.castest.TestAtomicState - t-1到达起跑线
13:57:46.619 [t-4] INFO com.walking.castest.TestAtomicState - t-4到达起跑线
13:57:46.628 [t-1] INFO com.walking.castest.TestAtomicState - 修改失败,tName:t-1
13:57:46.628 [t-4] INFO com.walking.castest.TestAtomicState - 修改成功,tName:t-4
13:57:46.628 [t-2] INFO com.walking.castest.TestAtomicState - 修改失败,tName:t-2
13:57:46.628 [t-5] INFO com.walking.castest.TestAtomicState - 修改失败,tName:t-5
13:57:46.628 [t-3] INFO com.walking.castest.TestAtomicState - 修改失败,tName:t-3
13:57:46.636 [main] INFO com.walking.castest.TestAtomicState - state最后的值:4

可以看到只有一个线程执行成功,这就是CAS的基本使用。

CAS的ABA问题

何为ABA问题呢?举个例子,小明和小花合伙卖煎饼,不就后攒了10万元,他们一起去银行把钱存在他们公共的账户里,但是小明听说最近牛市来了,就偷偷的把钱转移到了股票市场,公共账户余额是0。1个月后股票赚了一笔钱,然后小明把之前转移的10万元又存到他们的公共账户。小明和小花一个月后又去存钱,去查账户余额是10万。这就是ABA问题,简单来说就是一个值本来是A,两个线程同时都看到是A,然后线程1把A改成B后又改成A,线程1结束了。然后线程2去修改时,看到的是A,无法感知到这个过程中值发生过变化,对于线程2来说就发生了ABA的问题。

模拟ABA问题:

import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class CAS_ABA_Stampe {
 static AtomicInteger atomicInteger = new AtomicInteger(10);
 public static void main(String[] args) throws InterruptedException {
  Thread t1 = new Thread(() -> {
   try {
    log.info("{}拿到state的值为:{}", Thread.currentThread().getName(), atomicInteger.get());
    log.info("{}第一次修改", Thread.currentThread().getName());
    atomicInteger.getAndSet(0);
    Thread.sleep(2000);
    log.info("{}第二次修改", Thread.currentThread().getName());
    atomicInteger.getAndSet(10);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }, "t1");
  t1.start();

  Thread t2 = new Thread(() -> {
   try {
    log.info("{}第一次拿到state的值为:{}", Thread.currentThread().getName(), atomicInteger.get());
    Thread.sleep(2500);
    log.info("{}第二次拿到state的值为:{}", Thread.currentThread().getName(), atomicInteger.get());
    log.info("{}开始修改state的值为2", Thread.currentThread().getName());
    atomicInteger.getAndSet(20);
    log.info("{}修改成功", Thread.currentThread().getName());
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }, "t2");
  t2.start();
  t1.join();
  t2.join();
  log.info("最终state的值:{}", atomicInteger.get());
 }
}

//结果t2也能修改成功,并没有发现这种变化
15:12:35.999 [t1] INFO com.walking.castest.CAS_ABA_Stampe - t1拿到state的值为:10
15:12:35.999 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2第一次拿到state的值为:10
15:12:36.014 [t1] INFO com.walking.castest.CAS_ABA_Stampe - t1第一次修改
15:12:38.015 [t1] INFO com.walking.castest.CAS_ABA_Stampe - t1第二次修改
15:12:38.515 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2第二次拿到state的值为:10
15:12:38.515 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2开始修改state的值为2
15:12:38.516 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2修改成功
15:12:38.516 [main] INFO com.walking.castest.CAS_ABA_Stampe - 最终state的值:20

怎么解决CAS的ABA问题呢?

那就是基于版本号去解决,增加一个版本号的概念,每次被修改这个版本号就加1,版本号是一直向前的,版本号变了,就说明被修改过。

JUC包中提供了解决ABA问题的工具:

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicStampedReference;

@Slf4j
public class CAS_ABA_Stampe {
 static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(10, 1);

 public static void main(String[] args) throws InterruptedException {
  Thread t1 = new Thread(() -> {
   try {
    int stamp = stampedReference.getStamp();
    int intValue = stampedReference.getReference().intValue();
    log.info("{}私挪公款拿到stamp的值为:{},余额:{}", Thread.currentThread().getName(), stamp,intValue);
    stampedReference.compareAndSet(10, 0, stamp, stamp + 1);
    Thread.sleep(2000);
    stamp = stampedReference.getStamp();
    intValue = stampedReference.getReference().intValue();
    log.info("{}还回公款拿到stamp的值为:{},余额:{}", Thread.currentThread().getName(), stamp,intValue);
    stampedReference.compareAndSet(0, 10, stamp, stamp + 1);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }, "t1");
  t1.start();

  Thread t2 = new Thread(() -> {
   try {
    int stamp = stampedReference.getStamp();
    int intValue = stampedReference.getReference().intValue();
    log.info("{}拿到stamp的值为:{},余额:{}", Thread.currentThread().getName(), stamp, intValue);
    Thread.sleep(3000);

    log.info("{}开始存款", Thread.currentThread().getName());
    if (stampedReference.compareAndSet(10, 20, stamp, stamp + 1)) {
     log.info("{}款款成功", Thread.currentThread().getName());
    }else {
     log.info("{}存款失败,发现账户异常!!oldStamp:{},currentStamp:{}", Thread.currentThread().getName(),stamp,stampedReference.getStamp());
    }
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }, "t2");
  t2.start();
  t1.join();
  t2.join();
  log.info("最终账户余额:{}W", stampedReference.getReference().intValue());
 }
}

运行结果:

15:32:37.488 [t1] INFO com.walking.castest.CAS_ABA_Stampe - t1私挪公款拿到stamp的值为:1,余额:10
15:32:37.476 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2拿到stamp的值为:1,余额:10
15:32:39.500 [t1] INFO com.walking.castest.CAS_ABA_Stampe - t1还回公款拿到stamp的值为:2,余额:0
15:32:40.498 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2开始存款
15:32:40.498 [t2] INFO com.walking.castest.CAS_ABA_Stampe - t2存款失败,发现账户异常!!oldStamp:1,currentStamp:3
15:32:40.498 [main] INFO com.walking.castest.CAS_ABA_Stampe - 最终账户余额:10W

t2存款时就发现账户异常,因为版本号已经变成了3,和t2刚开始拿到的不一样,说明已经被别人修改过,从而解决ABA问题。

到这里CAS就完啦。别忘了点赞,转发。

以上就是深入分析Java并发编程之CAS的详细内容,更多关于Java并发编程之CAS的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java并发编程总结——慎用CAS详解

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

  • java并发编程专题(五)----详解(JUC)ReentrantLock

    上一节我们了解了Lock接口的一些简单的说明,知道Lock锁的常用形式,那么这节我们正式开始进入JUC锁(java.util.concurrent包下的锁,简称JUC锁).下面我们来看一下Lock最常用的实现类ReentrantLock. 1.ReentrantLock简介 由单词意思我们可以知道这是可重入的意思.那么可重入对于锁而言到底意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放.这模仿了 sy

  • java并发编程专题(一)----线程基础知识

    在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线程状态图: 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权. 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码. 4

  • java并发编程专题(二)----如何创建并运行java线程

    实现线程的两种方式 上一节我们了解了关于线程的一些基本知识,下面我们正式进入多线程的实现环节.实现线程常用的有两种方式,一种是继承Thread类,一种是实现Runnable接口.当然还有第三种方式,那就是通过线程池来生成线程,后面我们还会学习,一步一个脚印打好基础. Runnable接口: public interface Runnable { public abstract void run(); } Thread类: public class Thread implements Runnab

  • java并发编程之cas详解

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值.这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术. CAS的使用场景 在程序和算法中一个经常出现的模式就是"check and act"模式.先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作.下面是一个

  • java并发编程专题(四)----浅谈(JUC)Lock锁

    首先我们来回忆一下上一节讲过的synchronized关键字,该关键字用于给代码段或方法加锁,使得某一时刻它修饰的方法或代码段只能被一个线程访问.那么试想,当我们遇到这样的情况:当synchronized修饰的方法或代码段因为某种原因(IO异常或是sleep方法)被阻塞了,但是锁有没有被释放,那么其他线程除了等待以外什么事都做不了.当我们遇到这种情况该怎么办呢?我们今天讲到的Lock锁将有机会为此行使他的职责. 1.为什么需要Lock synchronized 是Java 语言层面的,是内置的关

  • Java CAS底层实现原理实例详解

    这篇文章主要介绍了Java CAS底层实现原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.CAS(compareAndSwap)的概念 CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制. CAS(V, A, B),V为内存地址.A为预期原值,B为新值.如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值.否则,说明已经被其他线程更新,处理器不做任何操作:无论哪种情

  • java并发编程专题(三)----详解线程的同步

    有兴趣的朋友可以回顾一下前两篇 java并发编程专题(一)----线程基础知识 java并发编程专题(二)----如何创建并运行java线程 在现实开发中,我们或多或少的都经历过这样的情景:某一个变量被多个用户并发式的访问并修改,如何保证该变量在并发过程中对每一个用户的正确性呢?今天我们来聊聊线程同步的概念. 一般来说,程序并行化是为了获得更高的执行效率,但前提是,高效率不能以牺牲正确性为代价.如果程序并行化后, 连基本的执行结果的正确性都无法保证, 那么并行程序本身也就没有任何意义了.因此,

  • Java CAS基本实现原理代码实例解析

    一.前言 了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称.它有核心就是CAS与AQS.CAS是java.util.concurrent.atomic包的基础,如AtomicInteger.AtomicBoolean.AtomicLong等等类都是基于CAS. 什么是CAS呢?全称Compare And Swap,比较并交换.CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N.当且仅当预期值E和内存值V相同时,将内存值V修改为N

  • 深入分析Java并发编程之CAS

    在Java并发编程的世界里,synchronized 和 Lock 是控制多线程并发环境下对共享资源同步访问的两大手段.其中 Lock 是 JDK 层面的锁机制,是轻量级锁,底层使用大量的自旋+CAS操作实现的. 学习并发推荐<Java并发编程的艺术> 那什么是CAS呢?CAS,compare and swap,即比较并交换,什么是比较并交换呢?在Lock锁的理念中,采用的是一种乐观锁的形式,即多线程去修改共享资源时,不是在修改之前就加锁,而是乐观的认为没有别的线程和自己争锁,就是通过CAS的

  • Java并发编程之Condition源码分析(推荐)

    Condition介绍 上篇文章讲了ReentrantLock的加锁和释放锁的使用,这篇文章是对ReentrantLock的补充.ReentrantLock#newCondition()可以创建Condition,在ReentrantLock加锁过程中可以利用Condition阻塞当前线程并临时释放锁,待另外线程获取到锁并在逻辑后通知阻塞线程"激活".Condition常用在基于异步通信的同步机制实现中,比如dubbo中的请求和获取应答结果的实现. 常用方法 Condition中主要的

  • 浅谈Java并发编程之Lock锁和条件变量

    简单使用Lock锁 Java 5中引入了新的锁机制--java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接口有3个实现它的类:ReentrantLock.ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁.读锁和写锁.lock必须被显式地创建.锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例

  • Java并发编程之ConcurrentLinkedQueue源码详解

    一.ConcurrentLinkedQueue介绍 并编程中,一般需要用到安全的队列,如果要自己实现安全队列,可以使用2种方式: 方式1:加锁,这种实现方式就是我们常说的阻塞队列. 方式2:使用循环CAS算法实现,这种方式实现队列称之为非阻塞队列. 从点到面, 下面我们来看下非阻塞队列经典实现类:ConcurrentLinkedQueue (JDK1.8版) ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全的队列.当我们添加一个元素的时候,它会添加到队列的尾部,当我们

  • Java并发编程之ReentrantLock实现原理及源码剖析

    目录 一.ReentrantLock简介 二.ReentrantLock使用 三.ReentrantLock源码分析 1.非公平锁源码分析 2.公平锁源码分析 前面<Java并发编程之JUC并发核心AQS同步队列原理剖析>介绍了AQS的同步等待队列的实现原理及源码分析,这节我们将介绍一下基于AQS实现的ReentranLock的应用.特性.实现原理及源码分析. 一.ReentrantLock简介 ReentrantLock位于Java的juc包里面,从JDK1.5开始出现,是基于AQS同步队列

  • Java并发编程之JUC并发核心AQS同步队列原理剖析

    目录 一.AQS介绍 二.AQS中的队列 1.同步等待队列 2.条件等待队列 3.AQS队列节点Node 三.同步队列源码分析 1.同步队列分析 2.同步队列--独占模式源码分析 3.同步队列--共享模式源码分析 一.AQS介绍 队列同步器AbstractQueuedSynchronizer(简称AQS),AQS定义了一套多线程访问共享资源的同步器框架,是用来构建锁或者其他同步组件的基础框架,是一个依赖状态(state)的同步器.Java并发编程的核心在java.util.concurrent(

  • 深入理解Java并发编程之LinkedBlockingQueue队列

    前面一篇文章我们介绍了使用CAS算法实现的非阻塞队列ConcurrentLinedQueue, 下面我们来介绍使用独占锁实现的阻塞队列LinkedBlockingQueue. LinkedBlockingQueue也是使用单向链表实现的,其也有两个Node,分别用来存放首.尾节点,并且还有一个初始值为0的原子变量count,用来记录队列元素个数.另外还有两个ReentrantLock的实例,分别用来控制元素入队和出队的原子性,其中takeLock用来控制同时只有一个线程可以从队列头获取元素,其他

  • Java并发编程之StampedLock锁介绍

    StampedLock: StampedLock是并发包里面JDK8版本新增的一个锁,该锁提供了三种模式的读写控制,当调用获取锁的系列函数时,会返回一个long 型的变量,我们称之为戳记(stamp),这个戳记代表了锁的状态.其中try 系列获取锁的函数,当获取锁失败后会返回为0的stamp值.当调用释放锁和转换锁的方法时需要传入获取锁时返回的stamp值. StampedLock提供的三种读写模式的锁分别如下: 写锁witeLock: 是一个排它锁或者独占锁,某时只有一个线程可以获取该锁,当一

  • Java并发编程之ConcurrentLinkedQueue队列详情

    ConcurrentLinkedQueue JDK中提供了一系列场景的并发安全队列.总的来说,按照实现方式的不同可分为阻塞队列和非阻塞队列,前者使用锁实现,而后则使用CAS非阻塞算法实现. ConcurrentLinkedQueue 内部的队列使用单向链表方式实现,其中有两个volatile 类型的 Node 节点分别用来存放队列的首.尾节点.从下面的无参构造函数可知,默认头.尾节点都是指向 item 为null 的哨兵节点.新元素会被插入队列末尾,出队时从队列头部获取一个元素. public

随机推荐