ReentrantLock

ReentrantLock是可重入锁
分为公平锁和非公平锁。两者有什么区别?

NonfairSync

  1. static final class NonfairSync extends Sync {
  2. private static final long serialVersionUID = 7316153563782823691L;
  3. /**
  4. * Performs lock. Try immediate barge, backing up to normal
  5. * acquire on failure.
  6. */
  7. final void lock() {
  8. if (compareAndSetState(0, 1))
  9. setExclusiveOwnerThread(Thread.currentThread());
  10. else
  11. acquire(1);
  12. }
  13. protected final boolean tryAcquire(int acquires) {
  14. return nonfairTryAcquire(acquires);
  15. }
  16. }

FairSync

  1. static final class FairSync extends Sync {
  2. private static final long serialVersionUID = -3000897897090466540L;
  3. final void lock() {
  4. acquire(1);
  5. }
  6. /**
  7. * Fair version of tryAcquire. Don't grant access unless
  8. * recursive call or no waiters or is first.
  9. */
  10. protected final boolean tryAcquire(int acquires) {
  11. final Thread current = Thread.currentThread();
  12. int c = getState();
  13. if (c == 0) {
  14. if (!hasQueuedPredecessors() &&
  15. compareAndSetState(0, acquires)) {
  16. setExclusiveOwnerThread(current);
  17. return true;
  18. }
  19. }
  20. else if (current == getExclusiveOwnerThread()) {
  21. int nextc = c + acquires;
  22. if (nextc < 0)
  23. throw new Error("Maximum lock count exceeded");
  24. setState(nextc);
  25. return true;
  26. }
  27. return false;
  28. }
  29. }

两者的区别

  • 都会调用AQS提供的acquire()方法
  • unFairLock(非公平锁)会先进行一次CAS操作看看能不能获取到状态
  • FairLock和unFairLock分别自定义实现模板方法tryAcquire,公平锁会加多一个hasQueuedPredecessors方法的判断,也就是说公平锁会考虑先来后到的原则,如果前面有节点或者线程在排队那么他就放弃争夺。换成非公平锁的话,他会优先通过CAS进行争夺,忽略前面排队的节点或线程,抢夺成功的话那么就比早来的线程先获取到了锁。
  • 后续操作一样,但仍然会涉及到tryAcquire的调用
  • 总结:两者的加锁主要区别于第一次的尝试,尝试失败后都需要加入等待队列中,

然后不断轮训+dump直到获取到状态

  1. //FairLock的tryAcquire
  2. protected final boolean tryAcquire(int acquires) {
  3. final Thread current = Thread.currentThread();
  4. int c = getState();
  5. if (c == 0) {
  6. if (!hasQueuedPredecessors() &&
  7. compareAndSetState(0, acquires)) {
  8. setExclusiveOwnerThread(current);
  9. return true;
  10. }
  11. }
  12. else if (current == getExclusiveOwnerThread()) {
  13. int nextc = c + acquires;
  14. if (nextc < 0)
  15. throw new Error("Maximum lock count exceeded");
  16. setState(nextc);
  17. return true;
  18. }
  19. return false;
  20. }
  21. //unFairLock的tryAcquire
  22. protected final boolean tryAcquire(int acquires) {
  23. return nonfairTryAcquire(acquires);
  24. }
  25. final boolean nonfairTryAcquire(int acquires) {
  26. final Thread current = Thread.currentThread();
  27. int c = getState();
  28. if (c == 0) {
  29. if (compareAndSetState(0, acquires)) {
  30. setExclusiveOwnerThread(current);
  31. return true;
  32. }
  33. }
  34. else if (current == getExclusiveOwnerThread()) {
  35. int nextc = c + acquires;
  36. if (nextc < 0) // overflow
  37. throw new Error("Maximum lock count exceeded");
  38. setState(nextc);
  39. return true;
  40. }
  41. return false;
  42. }

ReentranReadWriteLock

读写锁,内部分别通过两个内部类ReadLock和WriteLock分别实现

  1. final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  2. final Lock readLock = rwl.readLock();
  3. final Lock writeLock = rwl.writeLock();

读写锁原理

读写锁的状态依然是利用state控制,不同的是读锁的获取是共享式的(意味允许多个线程同时获取),而写锁的获取是独占式的
读锁的状态取的是state字段的高16位,而写锁的状态取的是低16位
image.png
读锁状态获取
image.png
写锁状态获取
image.png

锁降级

锁降级指的是写锁降为读锁的过程。以下过程中,在写锁释放前,先获取了读锁再释放写锁,这样做的目的是为了防止脏读,同时又可以提高线程访问共享变量的效率。

  1. //伪代码
  2. //获取写锁
  3. writeLock.lock();
  4. //修改数据
  5. set a = 1;
  6. //获取读锁
  7. readLock.lock();
  8. //释放写锁
  9. writeLock.unlock();
  10. //开始进行其他数据处理

StampedLock

相比于ReentrantReadWriteLock,StampedLock的读是乐观读,在读过程中运行有线程写入,当有线程写入时,读的线程能够发现。