ReentrantLock
是可重入的独占锁, 同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被放入该锁的AQS
阻塞队列里面。
ReentrantLock
根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。其中Sync 类直接继承自AQS , 它的子类NonfairSync
和FairSync
分别实现了获取锁的非公平与公平策略。
1 2 3 4 5 6 public ReentrantLock () { sync = new NonfairSync (); } public ReentrantLock (boolean fair) { sync = fair ? new FairSync () : new NonfairSync (); }
state的含义 在ReentrantLock
中, AQS 的state
状态值表示线程获取该锁的可重入次数 , 在默认情况下, state
的值为0 表示当前锁没有被任何线程持有。当一个线程第一次获取该锁时会尝试使用CAS设置state
的值为1,如果CAS 成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置为2 , 这就是可重入次数。在该线程释放该锁时,会尝试使用CAS 让状态值减1 , 如果减1后状态值为0,则当前线程释放该锁。
锁的获取 ReentrantLock
的lock()
委托给了sync
类,根据创建ReentrantLock
构造函数选择sync 的实现是NonfairSync
还是FairSync
。
1 2 3 public void lock () { sync.lock(); }
非公平锁的获取 默认AQS 的状态值为0,所以第一个调用lock
的线程会通过CAS 设置状态值为1, CAS 成功则表示当前线程获取到了锁, 然后setExclusiveOwnerThread
设置该锁持有者是当前线程。如果这时候有其他线程调用lock
方法企图获取该锁, CAS 会失败,然后会调用AQS的acquire
方法。
1 2 3 4 5 6 7 8 final void lock () { if (compareAndSetState(0 , 1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1 ); }
AQS 并没有提供可用的tryAcquire
方法, tryAcquire
方法需要子类自己定制化,ReentrantLock
需要重写tryAcquire
方法。
1 2 3 protected final boolean tryAcquire (int acquires) { return nonfairTryAcquire(acquires); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 final boolean nonfairTryAcquire (int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0 ) { if (compareAndSetState(0 , acquires)) { setExclusiveOwnerThread(current); return true ; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; }
nonfairTryAcquire
方法首先会查看当前锁的状态值是否为0 ,为0则说明当前该锁空闲,那么就尝试CAS 获取该锁,将AQS 的状态值从0 设置为1 ,并设置当前锁的持有者为当前线程然后返回 true 。如果当前状态值不为0 则说明该锁已经被某个线程持有,如果当前线程是该锁的持有者,则状态值加1 ,然后返回true 。 如果当前线程不是锁的持有者或者CAS获取锁失败则返回false,然后其会被放入AQS 阻塞队列。
需要注意的是,线程在CAS 获取锁前,并不会查看当前AQS 队列里面是否有比自己更早请求该锁的线程, 而是使用了抢夺策略,这就是不公平的体现。
公平锁的获取 公平锁和非公平锁的获取不同之处在于重写的tryAcquire
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected final boolean tryAcquire (int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0 ) { if (!hasQueuedPredecessors() && compareAndSetState(0 , acquires)) { setExclusiveOwnerThread(current); return true ; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; }
公平锁和非公平锁的tryAcquire
代码的类似,不同之处在于,公平锁在设置CAS 获取锁前添加了hasQueuedPredecessors
方法检查当前线程是否有前驱节点 ,该方法是实现公平性的核心代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public final boolean hasQueuedPredecessors () { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
如果当前线程节点有前驱节点则返回true,否则如果当前AQS 队列为空或者当前线程节点是AQS 的第一个节点则返回false。
锁的释放 如果当前线程持有该锁, 则调用该方法会让该线程对该线程持有的AQS状态值减1, 如果减去1 后当前状态值为0 ,则当前线程会释放该锁, 否则仅仅减1而己。同样.ReentrantLock
需要重写tryRelease
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected final boolean tryRelease (int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException (); boolean free = false ; if (c == 0 ) { free = true ; setExclusiveOwnerThread(null ); } setState(c); return free; }
可重入锁使用案例 下面使用ReentrantLock
来实现一个简单的线程安全的list。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class ReentrantLockList <T> { List<T> list = new ArrayList <>(); final ReentrantLock lock=new ReentrantLock (); void add (T e) { lock.lock(); try { list.add(e); } catch (Exception exception) { exception.printStackTrace(); } finally { lock.unlock(); } } void remove (int index) { lock.lock(); try { list.remove(index); } catch (Exception exception) { exception.printStackTrace(); } finally { lock.unlock(); } } T get (int index) { lock.lock(); try { return list.get(index); } catch (Exception exception) { exception.printStackTrace(); } finally { lock.unlock(); } return null ; } }
如上代码通过在操作list
前进行加锁保证同一时间只有一个线程可以对list
数组进行修改,但是也只能有一个线程对list
元素进行访问。