主要是通过 CAS 方式实现的,是一种无锁算法。在没有线程被阻塞的情况下实现多线程之间的变量同步。JUC 包中的原子类就是通过 CAS 来实现了乐观锁。
<p align=center></p>
<p align=center> </p>
<p align=center></p>
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // unsafe: 获取并操作内存的数据。 private static final Unsafe U = Unsafe.getUnsafe(); // VALUE: 存储value在AtomicInteger中的偏移量。 private static final long VALUE; // value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的。 private volatile int value; static { try { VALUE = U.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (ReflectiveOperationException e) { throw new Error(e); } } }// ------------------------- JDK 8 -------------------------// AtomicInteger 自增方法public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}// Unsafe。classpublic final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}// ------------------------- OpenJDK 8 -------------------------// Unsafe。javapublic final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v;}
<p align=center></p>
<p align=center></p>
CAS 虽然很高效,但是 CAS 有三大缺陷:
<p align=center></p>
缺陷一: ABA 问题。CAS 需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是 A,后来变成了 B,然后又变成了 A,那么 CAS 进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA 问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。
JDK 从 1.5 开始提供了 AtomicStampedReference 类来解决 ABA 问题,具体操作封装在 compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
缺陷二: 循环时间长开销大。CAS 操作如果长时间不成功,会导致其一直自旋,给 CPU 带来非常大的开销。
//可重入,就是可以重复获取相同的锁, //synchronized和ReentrantLock都是可重入的 // 可重入降低了编程复杂性 public class WhatReentrant2 { public static void main (String[] args) { ReentrantLock lock = new ReentrantLock (); new Thread ( new Runnable () { @Override public void run () { try { lock. lock (); System.out. println ( "第1次获取锁,这个锁是:" + lock); int index = 1 ; while ( true ) { try { lock. lock (); System.out. println ( "第" + (++index) + "次获取锁,这个锁是:" + lock); try { Thread. sleep ( new Random (). nextInt ( 200 )); } catch (InterruptedException e) { e。 printStackTrace (); } if (index == 10 ) { break ; } } finally { lock. unlock (); } } } finally { lock. unlock (); } } }). start (); }
不可重入锁
public class Lock{private boolean isLocked = false;public synchronized void lock() throws InterruptedException{while(isLocked){wait();}isLocked = true;}public synchronized void unlock(){isLocked = false;notify(); }}public class Count{Lock lock = new Lock();public void print(){lock.lock();doAdd();lock.unlock();}public void doAdd(){lock.lock();//do somethinglock.unlock(); }}
关于可重入锁我们可以看一下下图进行图形化理解:
关于非可重入锁我们可以看一下下图进行图形化理解:
为什么可重入锁就可以在嵌套调用时可以自动获得锁呢?
ReentrantLock 和 NonReentrantLock 都继承父类 AQS。
其父类 AQS 中维护了一个同步状态 status 来计数重入次数,status 初始值为 0。当线程尝试获取锁时,可重入锁先尝试获取并更新 status 值,如果 status == 0 表示没有其他线程在执行同步代码,则把 status 置为 1,当前线程开始执行。
如果 status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行 status+1,且当前线程可以再次获取锁。
非可重入锁是直接去获取并尝试更新当前 status 的值,如果 status != 0 的话会导致其获取锁失败,当前线程阻塞。
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 取到当前锁的个数 int w = exclusiveCount(c); // 取写锁的个数w if (c != 0) { // 如果已经有线程持有了锁(c!=0) // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) // 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程不是当前线程就返回失败 return false; if (w + exclusiveCount(acquires) > MAX_COUNT) // 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。 throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。 return false; setExclusiveOwnerThread(current); // 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者 return true;}
释放锁时,可重入锁同样先获取当前 status 的值,在当前线程是持有锁的线程的前提下。如果 status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将 status 置为 0,将锁释放。
public void unlock() { sync。releaseShared(1);}//.............................................public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false;}//.............................................protected final boolean tryReleaseShared(int unused) { // ............... for (;;) { // 可重入锁同样先获取当前status的值, int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // 在当前线程是持有锁的线程的前提下。如果status-1 == 0,调用doReleaseShared return nextc == 0; }}//.............................................// 真正释放锁private void doReleaseShared() {}
重入锁 ReentrantLock 以及非可重入锁 NonReentrantLock 的源码来对比分析为什么非可重入锁在重复调用同步资源时会出现死锁。