java做登录时要加锁吗_你用对锁了吗?谈谈 Java “锁” 事

先来盘一盘为什么需要锁这玩意,这得从并发 BUG 的源头说起。

并发 BUG 的源头

这个问题我 19 年的时候写过一篇文章, 现在回头看那篇文章真的是羞涩啊。

e2795395837167d9cbf70c5c61bf6ac0.png

让我们来看下这个源头是什么,我们知道电脑有CPU、内存、硬盘,硬盘的读取速度最慢,其次是内存的读取,内存的读取相对于 CPU 的运行又太慢了,因此又搞了个CPU缓存,L1、L2、L3。

正是这个CPU缓存再加上现在多核CPU的情况产生了并发BUG。

3a59e3735fbb1b494f977197fa3f46f5.png

这就一个很简单的代码,如果此时有线程 A 和线程 B 分别在 CPU - A 和 CPU - B 中执行这个方法,它们的操作是先将 a 从主存取到 CPU 各自的缓存中,此时它们缓存中 a 的值都是 0。

然后它们分别执行 a++,此时它们各自眼中 a 的值都是 1,之后把 a 刷到主存的时候 a 的值还是1,这就出现问题了,明明执行了两次加一最终的结果却是 1,而不是 2。

这个问题就叫可见性问题。

在看我们 a++ 这条语句,我们现在的语言都是高级语言,这其实和语法糖很类似,用起来好像很方便实际上那只是表面,真正需要执行的指令一条都少不了。

高级语言的一条语句翻译成 CPU 指令的时候可不止一条, 就例如 a++ 转换成 CPU 指令至少就有三条。把 a 从内存拿到寄存器中;

在寄存器中 +1;

将结果写入缓存或内存中;

所以我们以为 a++ 这条语句是不可能中断的是具备原子性的,而实际上 CPU 可以能执行一条指令时间片就到了,此时上下文切换到另一个线程,它也执行 a++。再次切回来的时候 a 的值其实就已经不对了。

这个问题叫做原子性问题。

并且编译器或解释器为了优化性能,可能会改变语句的执行顺序,这叫指令重排,最经典的例子莫过于单例模式的双重检查了。而 CPU 为了提高执行效率,还会乱序执行,例如 CPU 在等待内存数据加载的时候发现后面的加法指令不依赖前面指令的计算结果,因此它就先执行了这条加法指令。

这个问题就叫有序性问题。

至此已经分析完了并发 BUG 的源头,即这三大问题。可以看到不管是 CPU 缓存、多核 CPU 、高级语言还是乱序重排其实都是必要的存在,所以我们只能直面这些问题。

而解决这些问题就是通过禁用缓存、禁止编译器指令重排、互斥等手段,今天我们的主题和互斥相关。

互斥就是保证对共享变量的修改是互斥的,即同一时刻只有一个线程在执行。而说到互斥相信大家脑海中浮现的就是锁。没错,我们今天的主题就是锁!锁就是为了解决原子性问题。

说到锁可能 Java 的同学第一反应就是 synchronized 关键字,毕竟是语言层面支持的。我们就先来看看 synchronized,有些同学对 synchronized 理解不到位所以用起来会有很多坑。

synchronized 注意点

我们先来看一份代码,这段代码就是咱们的涨工资之路,最终百万是洒洒水的。而一个线程时刻的对比着我们工资是不是相等的。我简单说一下IntStream.rangeClosed(1,1000000).forEach,可能有些人对这个不太熟悉,这个代码的就等于 for 循环了100W次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中的主要可以分为两类:内置和显式。 1. 内置 内置Java 中最基本的,它是通过 synchronized 关键字来实现的。内置可以用于方法或代码块上,当一个线程进入 synchronized 代码块时,它会自动地获取。如果其他线程尝试获取同一把,它们就会被阻塞,直到该被释放。内置的优点是使用简单,但是缺点是灵活性不够,例如无法对进行条件等待的控制。 2. 显式 与内置相比,显式提供了更多的灵活性和控制力,例如可重入性、公平性和超时等待。Java 中的显式主要包括 ReentrantLock、ReentrantReadWriteLock、StampedLock 等。显式的优点是灵活性高,可以根据需要设置不同的属性和控制,但是缺点是使用较为复杂,需要手动获取和释放,如果使用不当可能会导致死等问题。 3. 可重入 可重入是指同一个线程可以多次获得同一把,而不会导致死Java 中的内置和 ReentrantLock 都是可重入。可重入的优点是方便使用,但是需要注意避免死问题。 4. 公平和非公平 公平是指多个线程按照请求的顺序获取,也就是说,先请求的线程先获取。非公平则没有这个限制,可能会导致某些线程一直获取不到Java 中的 ReentrantLock 支持公平和非公平的模式。 5. 乐观和悲观 乐观和悲观是指处理并发访问时的两种不同思路。悲观认为并发访问是常态,因此总是假定会发生冲突,每次访问都会加锁,这种方式可以确保数据的一致性,但是加锁的代价较高。乐观则相反,它认为并发冲突的概率较小,因此不加锁,而是通过版本号等方式来检测并发冲突并解决。Java 中的 CAS(Compare and Swap)就是一种乐观的实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值