首页 > 网名 正文
qq带彤字名字,qq飞车2字名字…

时间:2023-11-07 作者:佚名

(手机横屏看源码更方便)

问题

(1)自己动手写一个锁需要哪些知识?

(2)自己动手写一个锁到底有多简单?

(3)自己能不能写出来一个完美的锁?

简介

本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁、解锁操作。

本篇文章的目标二是通过自己动手写一个锁,能更好地理解后面章节将要学习的AQS及各种同步器实现的原理。

分析

自己动手写一个锁需要准备些什么呢?

首先,在上一章学习synchronized的时候我们说过它的实现原理是更改对象头中的MarkWord,标记为已加锁或未加锁。

但是,我们自己是无法修改对象头信息的,那么我们可不可以用一个变量来代替呢?

比如,这个变量的值为1的时候就说明已加锁,变量值为0的时候就说明未加锁,我觉得可行。

其次,我们要保证多个线程对上面我们定义的变量的争用是可控的,所谓可控即同时只能有一个线程把它的值修改为1,且当它的值为1的时候其它线程不能再修改它的值,这种是不是就是典型的CAS操作,所以我们需要使用Unsafe这个类来做CAS操作。

然后,我们知道在多线程的环境下,多个线程对同一个锁的争用肯定只有一个能成功,那么,其它的线程就要排队,所以我们还需要一个队列。

最后,这些线程排队的时候干嘛呢?它们不能再继续执行自己的程序,那就只能阻塞了,阻塞完了当轮到这个线程的时候还要唤醒,所以我们还需要Unsfae这个类来阻塞(park)和唤醒(unpark)线程。

基于以上四点,我们需要的神器大致有:一个变量、一个队列、执行CAS/park/unpark的Unsafe类。

大概的流程图如下图所示:

关于Unsafe类的相关讲解请参考彤哥之前发的文章:

【】

解决

一个变量

这个变量只支持同时只有一个线程能把它修改为1,所以它修改完了一定要让其它线程可见,因此,这个变量需要使用volatile来修饰。

private volatile int state;

CAS

这个变量的修改必须是原子操作,所以我们需要CAS更新它,我们这里使用Unsafe来直接CAS更新int类型的state。

当然,这个变量如果直接使用AtomicInteger也是可以的,不过,既然我们学习了更底层的Unsafe类那就应该用(浪)起来。

private boolean compareAndSetState(int expect, int update) { return un(this, stateOffset, expect, update); }

一个队列

队列的实现有很多,数组、链表都可以,我们这里采用链表,毕竟链表实现队列相对简单一些,不用考虑扩容等问题。

这个队列的操作很有特点:

放元素的时候都是放到尾部,且可能是多个线程一起放,所以对尾部的操作要CAS更新;

唤醒一个元素的时候从头部开始,但同时只有一个线程在操作,即获得了锁的那个线程,所以对头部的操作不需要CAS去更新。

private static class Node { // 存储的元素为线程 thread thread; // 前一个节点(可以没有,但实现起来很困难) Node prev; // 后一个节点【本篇文章由公众号“彤哥读源码”原创】 Node next; public Node() { } public Node(Thread thread, Node prev) { = thread; = prev; } } // 链表头 private volatile Node head; // 链表尾 private volatile Node tail; // 原子更新tail字段 private boolean compareAndSetTail(Node expect, Node update) { return un(this, tailOffset, expect, update); }

这个队列很简单,存储的元素是线程,需要有指向下一个待唤醒的节点,前一个节点可有可无,但是没有实现起来很困难,不信学完这篇文章你试试。

加锁

public void lock() { // 尝试更新state字段,更新成功说明占有了锁 if (compareAndSetState(0, 1)) { return; } // 未更新成功则入队 Node node = enqueue(); Node prev = node.prev; // 再次尝试获取锁,需要检测上一个节点是不是head,按入队顺序加锁 while != head || !compareAndSetState(0, 1)) { // 未获取到锁,阻塞 un(false, 0L); } // 下面不需要原子更新,因为同时只有一个线程访问到这里 // 获取到锁了且上一个节点是head // head后移一位 head = node; // 清空当前节点的内容,协助GC node.thread = null; // 将上一个节点从链表中剔除,协助GC node.prev = null; = null; } // 入队 private Node enqueue() { while (true) { // 获取尾节点 Node t = tail; // 构造新节点 Node node = new Node(), t); // 不断尝试原子更新尾节点 if (compareAndSetTail(t, node)) { // 更新尾节点成功了,让原尾节点的next指针指向当前节点 t.next = node; return node; } } }

(1)尝试获取锁,成功了就直接返回;

(2)未获取到锁,就进入队列排队;

(3)入队之后,再次尝试获取锁;

(4)如果不成功,就阻塞;

(5)如果成功了,就把头节点后移一位,并清空当前节点的内容,且与上一个节点断绝关系;

(6)加锁结束;

解锁

// 解锁 public void unlock() { // 把state更新成0,这里不需要原子更新,因为同时只有一个线程访问到这里 state = 0; // 下一个待唤醒的节点【本篇文章由公众号“彤哥读源码”原创】 Node next = ; // 下一个节点不为空,就唤醒它 if (next != null) { un); } }

(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); In(0, 1000).forEach(i -> new Thread(() -> { lock.lock(); try { In(0, 10000).forEach(j -> { count++; }); } finally { lock.unlock(); } // Sy().getName()); coun(); }, "tt-" + i).start()); coun(); Sy(count); }

运行这段代码的结果是总是打印出10000000(一千万),说明我们的锁是正确的、可靠的、完美的。

总结

(1)自己动手写一个锁需要做准备:一个变量、一个队列、Unsafe类。

(2)原子更新变量为1说明获得锁成功;

(3)原子更新变量为1失败说明获得锁失败,进入队列排队;

(4)更新队列尾节点的时候是多线程竞争的,所以要使用原子更新;

(5)更新队列头节点的时候只有一个线程,不存在竞争,所以不需要使用原子更新;

(6)队列节点中的前一个节点prev的使用很巧妙,没有它将很难实现一个锁,只有写过的人才明白,不信你试试^^

彩蛋

(1)我们实现的锁支持可重入吗?

答:不可重入,因为我们每次只把state更新为1。如果要支持可重入也很简单,获取锁时检测锁是不是被当前线程占有着,如果是就把state的值加1,释放锁时每次减1即可,减为0时表示锁已释放。

(2)我们实现的锁是公平锁还是非公平锁?

答:非公平锁,因为获取锁的时候我们先尝试了一次,这里并不是严格的排队,所以是非公平锁。

(3)完整源码

关注我的某某号“彤哥读源码”,后台回复“mylock”获取本章完整源码。

注:下一章我们将开始分析传说中的AQS,这章是基础,请各位老铁务必搞明白。

本文信息为网友自行发布旨在分享与大家阅读学习,文中的观点和立场与本站无关,如对文中内容有异议请联系处理。

本文链接:https://www.paituo.cc/wangming/1312633.html

  • 小编推荐

    qq带莹字名字 qq飞车2字名字

    qq带莹字名字,qq飞车2字名字,名字中带莹字好不好相关介绍,你似烈酒灼我芳华,我含笑喝下。奈何桥边几回首,一碗孟婆汤,难入喉。今日小编来推荐一组凄美的好听的古风网名。 红颜泪. 泪颜葬相思 欲归林。 暮光染指,岁月倾城 韶华倾负 九重吟 进公子心房. 發如雪ㄨ

    带昕字的qq名字—qq飞车2字名字?

    带昕字的qq名字,qq飞车2字名字,带昕字的男孩名字相关介绍,时间如梭,岁月如歌。稍不留神我们长大了,曾经挂在嘴边的2020年傻妞牌手机的诞生时间也马上就要到来了,在这个车水马龙的城市,稍有不慎就会变成白发老人了,算不上经过岁月的蹉跎,但这小小的一生,正在悄无声

    qq飞车2字名字少见qq飞车红名字怎么得

    qq飞车2字名字少见,qq飞车红名字怎么得,qq飞车温柔的名字相关介绍,《QQ飞车手游》既是一款竞技类游戏,也是一个大型的交友平台,因为在这里,我们可以结交到全国各地的朋友。既然是交友平台,那么为了交到朋友,我们也应该有着属于自己的交友名片,在《QQ飞车手游》里面,主页上

    “带5个字的qq名字“qq飞车2字名字?

    带5个字的qq名字,qq飞车2字名字,qq名字女生2个字相关介绍,你说十里红妆,盛世烟花;后来一等经年,为候竹马。今天,可爱点小编带来淡雅五字古风意境网名,希望古风迷们会喜欢... 一夕意相左 风追烟花雨 北巷长歌悠 落笔映浮华 凉了醉人心 檐下等归人 一曲一场叹

    qq飞车2字名字带寒字、qq飞车空白名字代码复制?

    qq飞车2字名字带寒字,qq飞车空白名字代码复制,qq飞车温柔的名字相关介绍,

    勋字QQ名字qq飞车2字名字…

    勋字QQ名字,qq飞车2字名字,qq名字男生2字相关介绍,名字是送给孩子最美好的礼物,对于名字的寓意,不同的父母有不同的要求。接下来就分享了安康、吉祥、富有的宝宝名字,有需要的父母可以从中参考合适的来起名。 安康、吉祥、富有的宝宝名字,送给孩子最美好的礼物!

    带静字qq名字、qq飞车2字名字?

    带静字qq名字,qq飞车2字名字,带静的qq名字大全集相关介绍,喜欢女孩 所以整个孕期就只想了女孩名字,叫任静好,小名好好。我喜欢一句话,叫琴瑟在御,岁月静好。我的名字又带个静字,好好就是我老公任某某和我某某静的好好。 想的是很好,事与愿违,生了个男孩[哈

    〔qq带彤字名字〕qq名字复杂的字

    qq带彤字名字,qq名字复杂的字,qq飞车2字名字相关介绍,近些年 提起父母给孩子起名 可谓是既花样百出又大同小异 如今“10后”的父母起起名来更是越来越潮了 近日发布的姓名大数据报告 《2019姓名全景报告-中国人起名质量首次量化揭示》(简称“报告”)显示