提到同步,我们一般首先想到的是lock,synchronized,但java中有一套更加轻量级的同步方式即atomic类。java的并发原子包里面提供了很多可以进行原子操作的类,比如:

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong
  • AtomicReference

下面以AtomicInteger类为例:

  1. package com.javaBase.LineDistance;
  2.  
  3. import java.util.concurrent.atomic.AtomicInteger;
  4.  
  5. /**
  6. * 〈一句话功能简述〉;
  7. * 〈功能详细描述〉
  8. *
  9. * @author jxx
  10. * @see [相关类/方法](可选)
  11. * @since [产品/模块版本] (可选)
  12. */
  13. public class TestAtomic {
  14.  
  15. public static AtomicInteger atomicInteger = new AtomicInteger(0);
  16.  
  17. public static void main(String[] args) throws InterruptedException{
  18. Thread t1 = new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. for (int i=0;i<1000;i++) {
  22. atomicInteger.incrementAndGet();
  23. }
  24. }
  25. });
  26. Thread t2 = new Thread(new Runnable() {
  27. @Override
  28. public void run() {
  29. for (int i=0;i<1000;i++) {
  30. atomicInteger.incrementAndGet();
  31. }
  32. }
  33. });
  34. t1.start();
  35. t2.start();
  36. t1.join();
  37. t2.join();
  38. System.out.println("最终结果:" + atomicInteger);
  39. }
  40. }

运行结果:

  1. 最终结果:2000

由结果可知,atomicInteger类是线程安全的。下面看看incrementAndGet()方法是如何实现的,源码如下:

  1. /**
  2. * Atomically increments by one the current value.
  3. *
  4. * @return the updated value
  5. */
  6. public final int incrementAndGet() {
  7. return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  8. }
  1.  
/** 其中getIntVolatile和compareAndSwapInt都是native方法
  * getIntVolatile是获取当前的期望值
  * compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值
  */
  1. public final int getAndAddInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  6.  
  7. return var5;
  8. }

  可以看到原子性的实现没有用synchronized,说明是非阻塞同步。最核心的方法是compareAndSwapInt,也就是所谓的CAS操作。CAS操作依赖底层硬件的CAS指令,CAS指令有两个步骤:冲突检测和更新操作,但是这两个步骤合起来成为一个原子性操作。CAS指令需要3个操作数:内存位置(V),旧的预期值(A),新值(B)。CAS指令执行时,首先比较内存位置V处的值和A的值是否相等(冲突检测),如果相等,就用新值B覆盖A(更新操作),否则,就什么也不做。所以,一般循环执行CAS操作,直到成功为止。

  1. private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
  1. static {
  2. try {
  3. valueOffset = unsafe.objectFieldOffset
  4. (AtomicInteger.class.getDeclaredField("value"));
  5. } catch (Exception ex) { throw new Error(ex); }
  6. }

  Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。 通常我们最好也不要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。

  CAS也并非完美的,它会导致ABA问题,就是说,当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。那么如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。在JDK中提供了AtomicStampedReference类来解决这个问题,思路是一样的。这个类也维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。

AtomicStampedReference使用代码:

  1. package com.wangjun.thread;
  2.  
  3. import java.util.concurrent.atomic.AtomicInteger;
  4. import java.util.concurrent.atomic.AtomicStampedReference;
  5.  
  6. public class ABA {
  7.  
  8. // 普通的原子类,存在ABA问题
  9. AtomicInteger a1 = new AtomicInteger(10);
  10. // 带有时间戳的原子类,不存在ABA问题,第二个参数就是默认时间戳,这里指定为0
  11. AtomicStampedReference<Integer> a2 = new AtomicStampedReference<Integer>(10, 0);
  12.  
  13. public static void main(String[] args) {
  14. ABA a = new ABA();
  15. a.test();
  16. }
  17.  
  18. public void test() {
  19. new Thread1().start();
  20. new Thread2().start();
  21. new Thread3().start();
  22. new Thread4().start();
  23. }
  24.  
  25. class Thread1 extends Thread {
  26. @Override
  27. public void run() {
  28. a1.compareAndSet(10, 11);
  29. a1.compareAndSet(11, 10);
  30. }
  31. }
  32. class Thread2 extends Thread {
  33. @Override
  34. public void run() {
  35. try {
  36. Thread.sleep(200); // 睡0.2秒,给线程1时间做ABA操作
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. System.out.println("AtomicInteger原子操作:" + a1.compareAndSet(10, 11));
  41. }
  42. }
  43. class Thread3 extends Thread {
  44. @Override
  45. public void run() {
  46. try {
  47. Thread.sleep(500); // 睡0.5秒,保证线程4先执行
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. int stamp = a2.getStamp();
  52. a2.compareAndSet(10, 11, stamp, stamp + 1);
  53. stamp = a2.getStamp();
  54. a2.compareAndSet(11, 10, stamp, stamp + 1);
  55. }
  56. }
  57. class Thread4 extends Thread {
  58. @Override
  59. public void run() {
  60. int stamp = a2.getStamp();
  61. try {
  62. Thread.sleep(1000); // 睡一秒,给线程3时间做ABA操作
  63. } catch (InterruptedException e) {
  64. e.printStackTrace();
  65. }
  66. System.out.println("AtomicStampedReference原子操作:" + a2.compareAndSet(10, 11, stamp, stamp + 1));
  67. }
  68. }
  69. }

可以看到使用AtomicStampedReference进行compareAndSet的时候,除了要验证数据,还要验证时间戳。如果数据一样,但是时间戳不一样,那么这个数据其实也被修改过了。

并发包中automic类的原理的更多相关文章

  1. 【转载】Lua中实现类的原理

    原文地址 http://wuzhiwei.net/lua_make_class/ 不错,将metatable讲的很透彻,我终于懂了. --------------------------------- ...

  2. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  3. Java并发包中常用类小结(一)

    从JDK1.5以后,Java为我们引入了一个并发包,用于解决实际开发中经常用到的并发问题,那我们今天就来简单看一下相关的一些常见类的使用情况. 1.ConcurrentHashMap Concurre ...

  4. 关于boost中enable_shared_from_this类的原理分析

    首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...

  5. Java并发包中线程池ThreadPoolExecutor原理探究

    一.线程池简介 线程池的使用主要是解决两个问题:①当执行大量异步任务的时候线程池能够提供更好的性能,在不使用线程池时候,每当需要执行异步任务的时候直接new一个线程来运行的话,线程的创建和销毁都是需要 ...

  6. Java并发包中CountDownLatch的工作原理、使用示例

    1. CountDownLatch的介绍 CountDownLatch是一个同步工具,它主要用线程执行之间的协作.CountDownLatch 的作用和 Thread.join() 方法类似,让一些线 ...

  7. Java并发包中CyclicBarrier的工作原理、使用示例

    1. CyclicBarrier的介绍与源码分析 CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时 ...

  8. Java并发包中Lock的实现原理

    1. Lock 的简介及使用 Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制.本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\l ...

  9. Java并发包中常用类小结(二)

    6.ThredPoolExecutor ThredPoolExecutor是基于命令模式下的一个典型的线程池的实现,主要通过一些策略实现一个典型的线程池,目前已知的策略有ThreadPoolExecu ...

随机推荐

  1. 快速搭建一套k8s集群环境

    参考官网 kubeadm是官方提供的快速搭建k8s集群的开源工具,对于非运维人员学习k8s,kubeadm方式安装相对更简单. kubeadm创建一个集群:https://kubernetes.io/ ...

  2. CSRF靶场练习

    实验目的 了解CSRF跨站伪造请求实验 实验原理 CSRF的原理 CSRF(Cross-site Request Forgery)是指跨站点请求伪造,也就是跨站漏洞攻击,通常用来指 WEB 网站的这一 ...

  3. [旧][Android] Retrofit 源码分析之执行流程

    备注 原发表于2016.04.23,资料已过时,仅作备份,谨慎参考 前言 由于是第一次自己翻看源代码进行学习,加上基础不好,在看源代码的过程中简直痛苦不堪,但同时也暴露出了自己的许多问题.我觉得学习源 ...

  4. Telnet拓展测试--在生产测试场景的应用

    本文关键词:流量测试.Telnet拓展测试.TCP/IP.时延 一.Telnet简介 Telnet协议是TCP/IP协议族中的一员,是Internet远程登录服务的标准协议和主要方式.它为用户提供了在 ...

  5. 【C#基础概念】程序集清单

    .NET Core 程序集(模块)还包含描述程序集本身的元数据,我们称之为清单.清单记录了当前程序集正常运行所需的所有外部程序集.程序集的版本号.版权信息.模块 .资源(图片 xml等)等.与类型元数 ...

  6. linux中rlwrap安装

    转至:https://www.cnblogs.com/hw-1015/p/6601294.html 在linux上使用sqlplus命令的时候,上下键.空格键.删除键都不能使用,非常麻烦.安装了rlw ...

  7. idea教程--如何申请免费的ideaIDE

    开始申请前请先到 https://www.jetbrains.com/zh/student/ 阅读免费学生授权的介绍和常见问题,再依照下方流程进行申请. (1)到 https://www.jetbra ...

  8. visual studio 快捷键重置及设置

    https://blog.csdn.net/glw0223/article/details/93195009

  9. MySQL日常笔记第二讲

    今日内容概要 存储引擎 MySQL的数据类型 约束条件 今日内容详细 存储引擎 """ 针对不同的数据可以有不同的存储方式 存储引擎就相当于针对数据采用不同的存储方式 & ...

  10. SQL实现一年中每个日期剔除节假日和星期天之后的五个日期是多少

    最近公司OA系统的需求,实现一年中每个日期剔除节假日和星期天之后的五个日期是几号,每个日期都要跳过节假日和星期天,当时是真的慌了,郁闷了一天,后来半夜忽然来灵感,想想还是可以实现. 需要做一张节假日的 ...