什么是原子操作?如何实现原子操作?
个人理解一个任务执行过程中不能打断必须按顺序执行且不可切割。
实现原子操作Java中可以用Synchronized或者锁机制,synchronized关键字是基于阻塞的锁机制 在多线程中存在一些问题。例如被阻塞的线程优先级很高很重要怎么办?如果获得锁的线程一直不释放锁怎么办?死锁 ?
CAS实现原理
CAS的实现是基于处理的cas执行实现,每个cas操作都有3个运算符 内存地址v,期望值A ,希望值B,如果这时候期望值等于A 那么直接修改成B CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。循环CAS就是在一个循环里不断的做cas操作,直到成功为止。CAS是一种乐观锁。实现原子操作还可以使用当前的处理器基本都支持CAS()的指令,只不过每个厂家所实现的算法并不一样,每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。
CAS工作原理
CAS存在的问题及解决方案
1.ABA 问题
假如存在3个线程A,B,C同时访问一个变量k=1 ,线程A先执行 +1 操作后 k=2 ,线程B执行-1操作后k=1,线程C再去访问的时候k的时候是原来没有修改的值吗?显然不是。这个就是CAS的ABA问题,修改完后变成原来的值CAS无法判断是否改变。
优化方案:给变量加版本号或者可以累加的标记 ,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。
2.性能 -循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3.只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。或者JDK提供了AtomicReference类来保证引用对象之间的原子性
Atomic目前支持AtomicInteger,AtomicIntegerArray,AtomicReference,AtomicStampedReference
AbstractQueuedSynchronizer (AQS)
队列同步器AbstractQueuedSynchronizer(以下简称同步器或AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,内部实现是CLH队列变种。
AQS使用的主要是继承 子类通过继承修改state这个值来修改当前状态。提供了同步器提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的。主要实现的类有(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
主要方法如下
aqs方法CLH队列
CLH队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。
当一个线程需要获取锁时:
1.创建一个QNode,将locked设置为true 标记需要锁去执行任务,myPred引用前驱节点
QNode2.线程A对tail调用getAndset()使其成为队列对尾部 同时获取前驱节点对引用myPred,线程B需要获得锁,同样的流程再来一遍
线程A获取锁 线程B获取锁3.线程就在前驱节点自旋 获取locked的值,一旦获取到locked为true 那就获取锁
4.当一个线程释放锁那么就将locked设置为true 同时收回前驱节点。
获得锁后前驱节点回收ReentrantLock
锁的可重入
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决以下两个问题。
1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
2)锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
nonfairTryAcquire方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。同步状态表示锁被一个线程重复获取的次数。
如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
公平和非公平锁
ReentrantLock的构造函数中,默认的无参构造函数将会把Sync对象创建为NonfairSync对象,这是一个“非公平锁”;而另一个构造函数ReentrantLock(boolean fair)传入参数为true时将会把Sync对象创建为“公平锁”FairSync。
nonfairTryAcquire(int acquires)方法,对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,而公平锁则不同。tryAcquire方法,该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。
网友评论