java手写一个ReentrantLock锁

发布时间:2022-03-01 11:50:12 作者:yexindonglai@163.com 阅读(431)

 

    ReentrantLock锁是一个轻量级锁,底层其实就是用自旋锁实现的,lock锁不依赖操作系统,而是使用java实现的锁,当我们调用lock方法的时候,在内部其实调用了Sync.lock()方法,而Sync继承了AbstractQueuedSynchronizer,简称AQS,所以在底层调用的其实是AQS的 lock() 方法;

 

 

ReentrantLock和synchronized不同的是,synchronized在jdk1.5以前是一把很重的锁,每次使用时都需要向操作系统申请,所以会耗费很大的资源,且效率不高,但ReentrantLock 的底层是由cas实现的,cas本身是自旋锁,也叫无锁,因为轻量级锁不需要像操作系统申请锁资源,所以不会进入阻塞状态,所以lock锁的效率要比synchronized高很多;

我们先看看lock锁的底层原理流程图

 

好了,知道了这些流程,那接下来我们自己实现一个简单的Lock锁吧!

自定义的锁   XdLock.java

  1. package com.test;
  2. import java.util.concurrent.LinkedBlockingQueue;
  3. import java.util.concurrent.TimeUnit;
  4. import java.util.concurrent.atomic.AtomicReference;
  5. import java.util.concurrent.locks.Condition;
  6. import java.util.concurrent.locks.Lock;
  7. import java.util.concurrent.locks.LockSupport;
  8. public
  9. // 手写 的 lock 锁
  10. class XdLock implements Lock {
  11. // 缓存任务的队列
  12. LinkedBlockingQueue<Thread> queue = new LinkedBlockingQueue<>();
  13. // 原子类 ,登记我们的线程
  14. AtomicReference<Thread> cas = new AtomicReference<>();
  15. // 上锁
  16. @Override
  17. public void lock() {
  18. // 使用cas进行尝试上锁, 如果为空,将AtomicReference的值设为当前线程
  19. while (!cas.compareAndSet(null, Thread.currentThread())) {
  20. // 抢锁锁失败了!
  21. queue.add(Thread.currentThread()); // 将线程进入队列,等候执行
  22. System.out.println(Thread.currentThread().getName() + ":进入阻塞");
  23. // 让当前线程阻塞,
  24. LockSupport.park(); // 注意:调用park() 方法后会进入阻塞状态,下面的代码就不会在执行了,除非别的线程调用了 unpark(Thread) 方法来唤醒当前线程
  25. System.out.println(Thread.currentThread().getName() + ":已放开阻塞线程");
  26. queue.remove(Thread.currentThread()); // 移除线程
  27. }
  28. }
  29. @Override
  30. public void lockInterruptibly() throws InterruptedException {
  31. }
  32. @Override
  33. public boolean tryLock() {
  34. return false;
  35. }
  36. @Override
  37. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  38. return false;
  39. }
  40. // 解锁
  41. @Override
  42. public void unlock() {
  43. // 只有持有锁的线程才能解锁,
  44. // cas.compareAndSet(Thread.currentThread(),null) 的意思是:如果 AtomicReference 记录的是当前线程,表示当前线程持有锁,解锁的操作就是设为null;
  45. if (cas.compareAndSet(Thread.currentThread(), null)) {
  46. // 唤醒其他等待的线程
  47. for (Object object : queue.toArray()) {
  48. Thread thread = (Thread) object;
  49. System.out.println(Thread.currentThread().getName() + ":进行唤醒操作");
  50. LockSupport.unpark(thread); // 唤醒其他阻塞的线程;
  51. System.out.println(Thread.currentThread().getName() + ":唤醒成功!!");
  52. }
  53. } else {
  54. // 解锁失败,不做任何操作
  55. }
  56. }
  57. @Override
  58. public Condition newCondition() {
  59. return null;
  60. }
  61. }

运行测试  LockTest.java

  1. package com.test;
  2. public class LockTest {
  3. private XdLock lock = null;
  4. public static void main(String[] args) {
  5. // 实例化锁
  6. XdLock lock = new XdLock();
  7. // 创建2个线程来争抢锁
  8. LockTest lockTest = new LockTest(lock);
  9. Thread thread = new Thread(lockTest::show);
  10. thread.setName("线程AA");
  11. Thread thread1 = new Thread(lockTest::show);
  12. thread1.setName("线程BB");
  13. thread.start();
  14. thread1.start();
  15. }
  16. // 构造方法
  17. public LockTest(XdLock lock) {
  18. this.lock = lock;
  19. }
  20. public void show() {
  21. lock.lock(); // 上锁
  22. int i = 100;
  23. while (i > 0) {
  24. System.out.println(Thread.currentThread().getName() + ":叶新东 " + i);
  25. i--;
  26. }
  27. lock.unlock(); // 解锁
  28. }
  29. }

 

首次上锁和二次上锁

ReentrantLock 的首次上锁和二次上锁流程又不一样,当给第一个线程上锁的时候,是不进入队列的,给第二个线程上锁的时候才会进入队列,超过2次以后每次上锁都会进入队列,上锁流程如下:

关键字Java