(6)加锁结束;
解锁 // 解锁 public void unlock() { // 把state更新成0,这里不需要原子更新,因为同时只有一个线程访问到这里 state = 0; // 下一个待唤醒的节点 Node next = head.next; // 下一个节点不为空,就唤醒它 if (next != null) { unsafe.unpark(next.thread); } }(1)把state改成0,这里不需要CAS更新,因为现在还在加锁中,只有一个线程去更新,在这句之后就释放了锁;
(2)如果有下一个节点就唤醒它;
(3)唤醒之后就会接着走上面lock()方法的while循环再去尝试获取锁;
(4)唤醒的线程不是百分之百能获取到锁的,因为这里state更新成0的时候就解锁了,之后可能就有线程去尝试加锁了。
测试上面完整的锁的实现就完了,是不是很简单,但是它是不是真的可靠呢,敢不敢来试试?!
直接上测试代码:
private static int count = 0; public static void main(String[] args) throws InterruptedException { MyLock lock = new MyLock(); CountDownLatch countDownLatch = new CountDownLatch(1000); IntStream.range(0, 1000).forEach(i -> new Thread(() -> { lock.lock(); try { IntStream.range(0, 10000).forEach(j -> { count++; }); } finally { lock.unlock(); } // System.out.println(Thread.currentThread().getName()); countDownLatch.countDown(); }, "tt-" + i).start()); countDownLatch.await(); System.out.println(count); }运行这段代码的结果是总是打印出10000000(一千万),说明我们的锁是正确的、可靠的、完美的。
总结(1)自己动手写一个锁需要做准备:一个变量、一个队列、Unsafe类。
(2)原子更新变量为1说明获得锁成功;
(3)原子更新变量为1失败说明获得锁失败,进入队列排队;
(4)更新队列尾节点的时候是多线程竞争的,所以要使用原子更新;
(5)更新队列头节点的时候只有一个线程,不存在竞争,所以不需要使用原子更新;
(6)队列节点中的前一个节点prev的使用很巧妙,没有它将很难实现一个锁,只有写过的人才明白,不信你试试^^
彩蛋(1)我们实现的锁支持可重入吗?
答:不可重入,因为我们每次只把state更新为1。如果要支持可重入也很简单,获取锁时检测锁是不是被当前线程占有着,如果是就把state的值加1,释放锁时每次减1即可,减为0时表示锁已释放。
(2)我们实现的锁是公平锁还是非公平锁?
答:非公平锁,因为获取锁的时候我们先尝试了一次,这里并不是严格的排队,所以是非公平锁。
(3)完整源码
关注我的公众号“彤哥读源码”,后台回复“mylock”获取本章完整源码。
注:下一章我们将开始分析传说中的AQS,这章是基础,请各位老铁务必搞明白。
推荐阅读死磕 java魔法类之Unsafe解析
死磕 java同步系列之JMM(Java Memory Model)
死磕 java同步系列之volatile解析
死磕 java同步系列之synchronized解析
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。