synchronized的总结

简介: synchronized的总结

🔎乐观锁与悲观锁

锁🔒冲突

多个线程针对同一个对象进行加锁🔒,可能产生阻塞等待

乐观锁🔒

预测接下来锁🔒冲突的概率不大

乐观锁总是假设最好的情况,认为一般情况下不会产生并发冲突,所以只在数据进行提交修改的时候去对数据进行冲突检测

悲观锁🔒

预测接下来锁🔒冲突的概率较大

悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据时都会上锁


🔎轻量级锁与重量级锁

轻量级锁🔒

加锁解锁的过程更快更高效

重量级锁🔒

加锁解锁的过程更慢更低效

注意

一个乐观锁🔒很可能也是一个轻量级锁🔒(不绝对)

一个悲观锁🔒很可能也是一个重量级锁🔒(不绝对)


🔎自旋锁与挂起等待锁

自旋锁🔒是轻量级锁的一种典型实现

挂起等待锁🔒是重量级锁的一种典型实现

举个栗子🥝

有一个老哥去追他喜欢的女神

问女神,你能做我女朋友吗

女神说:对不起,你是个好人

(老哥对女神加锁失败)

自旋锁🔒

这时候老哥选择继续每天向女神问候早安,午安,晚安

一旦哪一天,女神和她男朋友分手了

那老哥的机会就来了(有很大的机会加锁🔒成功)

自旋锁的意思就是一旦锁🔒被释放,就能第一时间拿到锁,速度更快

缺点是每天都得围着女神转(等待加锁),不能去做其他的事情(忙等,消耗cpu)

注意

自旋锁通常是纯用户态的,不需要经过内核态,时间相对较长

挂起等待锁🔒

这时候老哥选择果断放弃,潜心敲代码

等到哪一天女神分手(这中间不知道分手了多少个),最后找到了老哥求安慰,老哥再次尝试加锁

挂起等待锁的意思是锁🔒被释放,不能第一时间拿到锁,可能要很久才能拿到锁

优点是时间是空闲出来的(不用忙等,消耗cpu)

注意

挂起等待锁通过内核的机制(内核态)实现挂起等待,时间相对较长


🔎互斥锁与读写锁

互斥锁🔒

synchronized 只有两个操作

(1)进代码块,加锁

(2)出代码块,解锁

synchronized 是互斥锁🔒

加锁,就只是单纯的加锁,没由更进一步的划分了

读写锁🔒

(1)给读加锁

(2)给写加锁

(3)解锁

读写锁中约定

(1)读锁与读锁之间,不会产生锁竞争(不会影响程序速度)

(2)写锁与写锁之间,会产生锁竞争(会降低程序速度,但保证数据准确性)

(3)读锁与写锁之间,会产生锁竞争(会降低程序速度,但保证数据准确性)

注意

读写锁更适合于频繁读不频繁写的情况

Java标准库中提供了 ReentrantReadWriteLock 类,实现了读写锁

(1) ReentrantReadWriteLock.readLock 表示读一个锁

(2) ReentrantReadWriteLock.writeLock 表示写一个锁


🔎可重入锁与不可重入锁

可重入锁🔒

一个线程针对一把锁,连续加锁两次,没出现死锁,就是可重入锁🔒

不可重入锁🔒

一个线程针对一把锁,连续加锁两次,出现死锁,就是不可重入锁🔒

死锁🔒

举个栗子🥝

车钥匙忘在家里

家钥匙忘在车里

如果想拿到车钥匙就得先进入家里

如果想进入家里就得先拿到车钥匙

关于死锁🔒

case1🥝 一个线程一把锁

private static Object locker = new Object();
public static void main(String[] args) {
        synchronized (locker) {
            synchronized (locker) {
            }
        }
}

形如这样的代码,就是一个线程连续对一把锁加锁两次

加锁的时候判定一下看当前申请锁的线程是不是锁的拥有者(第一次加锁的线程)

如果是

第二次加锁成功(可重入锁🔒)

如果不是

第二次加锁需要等待第一次加锁的释放,第一次加锁的释放需要等待第二次加锁的成功

这样就形成了死锁(不可重入锁🔒)


case2🥝 两个线程两把锁

public class Test {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();
    static class Conuter {
        public static int count = 0;
        public void add1() {
            synchronized (locker1) {
                synchronized (locker2) {
                    count++;
                }
            }
        }
        public void add2() {
            synchronized (locker2) {
                synchronized (locker1) {
                    count++;
                }
            }
        }
    }
}

形如这样的代码,就是两个线程对两把锁进行加锁

线程1执行add1()方法,对 locker1进行加锁

此时线程2执行add1()方法,发现add1()方法被线程1加锁,只能等待

线程2执行add2()方法,对 locker2进行加锁

此时线程1执行add2()方法,发现add2()方法被线程2加锁,只能等待

由于add1()方法里面还需一个 locker2加锁,故线程1不能执行add1()方法

由于add2()方法里面还需一个 locker1加锁,故线程2不能执行add2()方法

但是线程2对locker2加锁需要等待线程1将 locker1释放

但是线程1对locker1加锁需要等待线程2将 locker2释放

于是就形成了死锁


case3🥝 M个线程N把锁(哲学家就餐问题)

当线程和锁的数量更多时,就更容易发生死锁问题了

举个栗子🥝

假设有5位哲学家

(1)每个人随机的进行吃面条(拿起筷子)和思考人生(放下筷子)

(2)这5位哲学家比较固执(如果他想拿筷子,却被别人占用了,就会进行等待,等待的过程中也不会放下手中的筷子)

注意

哲学家 ≈ 线程

筷子 ≈ 锁

假设5位哲学家同时拿起各自左手边的筷子,那么此时就会发生死锁🔒的情况(每个人都吃不到面条)

解决方案🌸

为筷子(锁)加编号

如果需要同时获取多把锁,约定加锁顺序(从编号小的开始加锁)

为了避免死锁的情况,给筷子(锁)加了编号

规定每个人先从编号小的筷子拿起,再拿编号大的筷子

于是

2号哲学家拿了筷子1

3号哲学家拿了筷子2

4号哲学家拿了筷子3

5号哲学家拿了筷子4

由于此时1号哲学家没能拿到编号较小的筷子1,所以他也不能拿到筷子5

这时5号哲学家拿起了筷子5,吃完后放下筷子(思考人生)

4号哲学家等5号哲学家放下筷子4,成功拿起筷子4,再结合手中的筷子3,顺利吃上面条

3号哲学家等4号哲学家放下筷子3,成功拿起筷子3,再结合手中的筷子2,顺利吃上面条

哲学家就餐问题就被完美解决了

对死锁🔒的总结

死锁的形成有四个必要条件

(1)互斥使用

一个线程拿到一把锁之后,另一个线程不能使用

(2)不可抢占

一个线程拿到锁之后,只能自己释放,其他线程不能强行占有(挖墙脚行为不道德)

(3)请求和保持

一个线程拿到一把锁之后(保持),继续申请其他的锁(请求)

一位哲学家拿到一根筷子后,不放下,继续申请其他的筷子

(4)循环等待

不戴口罩不能进药店,进药店才能买口罩戴


🔎公平锁与非公平锁

公平锁🔒

遵守先来后到

非公平锁🔒

不遵守先来后到

举个栗子🥝

一群老哥追一个女神

老哥A:我认识了一年

老哥B:我认识了5个月

老哥C:我认识了1个月

公平锁🔒

老哥A先来的,让老哥A先去追,

非公平锁🔒

凭啥你先来的你就能先追

大家各凭本事,谁追到算谁的

此处虽然是等概率竞争,但并未遵循先来后到原则,所以等概率也就成了非公平

🔎synchronized的特点

(1)既是乐观锁,也是悲观锁

(2)既是轻量级锁,也是重量级锁

(3)轻量级锁基于自旋锁实现,重量级锁基于挂起等待锁实现

(4)是互斥锁,不是读写锁

(5)是可重入锁

(6)是非公平锁

🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍

大家有什么不太理解的,可以私信或者评论区留言,一起加油

相关文章
|
2月前
|
算法 Java 编译器
Synchronized你又知道多少?
Synchronized 是 JVM 实现的一种互斥同步机制,通过 monitorenter 和 monitorexit 指令控制对象锁的获取与释放。锁的本质是对象头的标记,确保同一时间只有一个线程访问资源。Synchronized 支持可重入性,允许方法内部调用其他同步方法而不阻塞。JVM 对锁进行了优化,引入了自旋锁、偏向锁、轻量级锁和重量级锁,以减少系统开销。Synchronized 属于悲观锁,而乐观锁基于 CAS(Compare and Swap)算法实现非阻塞同步,提高并发性能。
63 8
|
6月前
|
Java
synchronized
synchronized
28 2
|
6月前
|
存储 安全 Java
|
安全 算法 Java
synchronized 同步锁
Java中的synchronized关键字用于实现线程同步,可以修饰方法或代码块。 1. 修饰方法:当一个方法被synchronized修饰时,只有获得该方法的锁的线程才能执行该方法。其他线程需要等待锁的释放才能执行该方法。 2. 修饰代码块:当某个对象被synchronized修饰时,任何线程在执行该对象中被synchronized修饰的代码块时,必须先获得该对象的锁。其他线程需要等待锁的释放才能执行同步代码块。Java中的每个对象都有一个内置锁,当一个对象被synchronized修饰时,它的内置锁就起作用了。只有获得该锁的线程才能访问被synchronized修饰的代码段。使用synch
62 0
ReentrantLock和Synchronized简单比较
ReentrantLock和Synchronized简单比较
46 0
|
Java
07.synchronized都问啥?
大家好,我是王有志。经过JMM和锁的铺垫,今天我们正式进入synchronized的内容,来看看关于synchronized面试中都会问啥?
61 1
07.synchronized都问啥?
Synchronized
作用:能够保证在同一时刻最多有一个线程执行该段代码,以保证并发的安全性。(当第一个线程去执行该段代码的时候就拿到锁,并独占这把锁,当方法执行结束或者一定条件后它才释放这把锁,在没释放锁之前,所有的线程处于等待状态)
72 0
|
前端开发 Java Spring
方法上加上 synchronized 就可以了么
方法上加上 synchronized 就可以了么
|
存储 缓存 安全
synchronized的简单理解
synchronized的简单理解
94 0