【Java】使用Atomic变量实现锁
Atomic原子操作
在 Java 5.0 提供了 java.util.concurrent
(简称JUC)包,在此包中增加了在并发编程中很常用的工具类
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。
- 原子更新基本类型类: AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 原子更新数组类:AtomicIntegerArray,AtomicLongArray
- 原子更新引用类型:AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
- 原子更新字段类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
详细介绍可以参考:Java中的Atomic包使用指南
Atomic的原理
下面通过AtomicInteger
的源码来看一下是怎么在没有锁的情况下保证数据正确性。首先看一下incrementAndGet()
方法的实现:
1 |
/** |
我们继续看,unsafe.getAndAddInt()
的实现是什么样的。
1 |
/** |
这是一个循环,offset是变量v在内存中相对于对象o起始位置的偏移,传给JNI层用来计算这个value的内存绝对地址。
然后找到JNI的实现代码,来看 native层的compareAndSwapInt()
方法的实现。这个方法的实现是这样的:
1 |
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) |
这个函数其实很简单,就是去看一下obj 的 offset 上的那个位置上的值是多少,如果是 e,那就把它更新为 x,返回true,如果不是 e,那就什么也不做,并且返回false。里面的核心方法是Atomic::compxchg()
,这个方法所属的类文件是在os_cpu目录下面,由此可以看出这个类是和CPU操作有关,进入代码如下:
1 |
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { |
这个方法里面都是汇编指令,看到LOCK_IF_MP
也有锁指令实现的原子操作,其实CAS也算是有锁操作,只不过是由CPU来触发,比synchronized性能好的多。
什么是CAS
CAS,Compare and Swap即比较并交换。 java.util.concurrent包借助CAS实现了区别于synchronized同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,所以不会上锁,但是在更新的时候会判断一下在此期间数据有没有更新。CAS有3个操作数:内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS的关键点在于,系统在硬件层面保证了比较并交换操作的原子性,处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。
CAS的优缺点
- CAS由于是在硬件层面保证的原子性,不会锁住当前线程,它的效率是很高的。
- CAS虽然很高效的实现了原子操作,但是它依然存在三个问题。
1、ABA问题。CAS在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是A,变成B,又变成A,那么CAS进行检查时会认为这个值没有变化,但是实际上却变化了。ABA问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就变成1A-2B-3A。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
2、并发越高,失败的次数会越多,CAS如果长时间不成功,会极大的增加CPU的开销。因此CAS不适合竞争十分频繁的场景。
3、只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
实现自旋锁
1 |
/** |
原理很简单,就是一直CAS抢锁,如果抢不到,就一直死循环,直到抢到了才退出这个循环。
自旋锁实现起来非常简单,如果关键区的执行时间很短,往往自旋等待会是一种比较高效的做法,它可以避免线程的频繁切换和调度。但如果关键区的执行时间很长,那这种做法就会大量地浪费CPU资源。
针对关键区执行时间长的情况,该怎么办呢?
实现可等待的锁
如果关键区的执行时间很长,自旋的锁会大量地浪费CPU资源,我们可以这样改进:当一个线程拿不到锁的时候,就让这个线程先休眠等待。这样,CPU就不会白白地空转了。大致步骤如下:
- 需要一个容器,如果线程抢不到锁,就把线程挂起来,并记录到这个容器里。
- 当一个线程放弃了锁,得从容器里找出一个挂起的线程,把它恢复了。
1 |
/** |
我们引入了一个 waitList,用于存储抢不到锁的线程,让它挂起。这里我们先借用一下JDK里的ConcurrentLinkedQueue
,因为这个Queue也是使用CAS操作实现的无锁队列,所以并不会引入JDK里的其他锁机制。如果大家去看AbstractQueuedSynchronizer
的实现,就会发现,它的acquire()
方法的逻辑与上面的实现是一样的。
不过上面的代码是不是没问题了呢?如果一个线程在还未调用park挂起之前,是不是有可能被其他线程先调用一遍unpark?这就是唤醒发生在休眠之前。发生这样的情况会不会带来问题呢?
【Java】使用Atomic变量实现锁的更多相关文章
- java线程:Atomic(原子的)
一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中 ...
- java 多线程总结篇4——锁机制
在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制.Java提供了多种多线程锁机制的实现方式,常见的有synchronized.ReentrantLock.Semaphore. ...
- java线程:Atomic(原子)
.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中, ...
- Java多线程专题5: JUC, 锁
合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...
- java并发库 Lock 公平锁和非公平锁
jdk1.5并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,关于两者区别,java并发编程实践里面有解释 公平锁: Threads acquir ...
- Atomic变量和Thread局部变量
Atomic变量和Thread局部变量 前面我们已经讲过如何让对象具有Thread安全性,让它们能够在同一时间在两个或以上的Thread中使用.Thread的安全性在多线程设计中非常重要,因为race ...
- Java中的双重检查锁(double checked locking)
最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...
- java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)
目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...
- Java多线程6:Synchronized锁代码块(this和任意对象)
一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...
随机推荐
- javascript闭包中循环问题
如下的html,为什么每次输出都是5,而不是点击每个p,就alert出对应的1,2,3,4,5. <html > <head> <meta http-equiv=&quo ...
- Android蓝牙自动配对Demo,亲测好使!!!
蓝牙自动配对,即搜索到其它蓝牙设备之后直接进行配对,不需要弹出配对确认框或者密钥输入框. 转载请注明出处http://blog.csdn.net/qq_25827845/article/details ...
- ArcGIS中国工具(ArcGISCTools)2.0正式发布
ArcGIS中国工具,简称CTools,集成在ArcMap10.0, ArcMap10.1, ArcMap10.2,安装就可以直接使用.主要有以下功能 1.接合图表生成2.图框工具3.制图工具4.图形 ...
- 01_java虚拟机基础入门
[Java虚拟机的基本结构] [ 1.类加载子系统 ] 负责从文件系统或者网络中加载Class信息,加载的信息存放在一块称之为方法区的内存空间. [ 2.方法区 ] 存放类信息.常量信息.常量池信息, ...
- 01_Mac下安装homebrew
参考:https://jingyan.baidu.com/album/fec7a1e5ec30341190b4e7e5.html?picindex=3 1.在打开的命令行工具中输入如下语句: ruby ...
- Android学习——BroadCast(二)
这篇博文介绍有序广播的使用 有序广播的发送 和普通广播完全相同,只需要更换发送的方式即可 ordered.setOnClickListener(new View.OnClickListener() { ...
- js前台检测上传图片大小的总结
最近一直在做上传图片的前端检测,不通过后台就完成这个动作.但实际是,实际效果差强人意. html5的fileApi出来后,对文件的处理才变得方便了些,对它的简单介绍可以看我的前面的博客.现在支持的浏览 ...
- FQDN说明
以下摘自百度百科: FQDN:(Fully Qualified Domain Name)完全合格域名/全称域名,是指主机名加上全路径,全路径中列出了序列中所有域成员.全域名可以从逻辑上准确地表示出主机 ...
- windows系统的错误码
https://blog.csdn.net/u011785544/article/details/51682290
- July 06th 2017 Week 27th Thursday
Knowledge is the antidote to fear. 知识可以解除恐惧. Fear always steps from unknown things. Once we know wha ...