【多线程】synchronized原理 | 锁升级| 锁消除 | 锁粗化 | 信号量 | CountDownLatch


一、synchronized原理

synchronized属于哪种锁?

​ 在初始情况下,synchronized会预测当前锁冲突的概率不大。此时以乐观锁的方式来运行(此时也是轻量级锁,以自旋锁的方式实现)。在实际使用的过程中,如果发现锁冲突的情况比较多,synchronized就会升级成悲观锁(也就是重量级锁,基于挂起等待的方式实现)。

1.对于“ 悲观乐观 ”,是自适应的

2.对于“ 重量轻量 ” ,是自适应的。

3.对于“ 自旋 和 挂起等待 ” ,也是自适应的。

4.不是读写锁。

5.是可重入锁。

6.是非公平锁。

1.锁升级

无锁 ->偏向锁->自旋锁(轻量级锁)->重量级锁

  • 锁升级的过程是单向的,不会降级。
  • 锁升级的过程,就是在 性能 和线性安全之间尽量进行权衡。
偏向锁:

线程池,优化的是创建新线程的效率(优化”找下一任“的效率),提前就创建好,直接拿来用,无缝衔接

偏向锁,(优化了”分手“的效率)

​ 偏向锁不是真的加锁,只是做了一个标记。即使后续要解锁,因为本来就没有加锁,也不用真的去释放锁。也就省下了加锁解锁的开销。当锁冲突出现的时候,偏向锁就会升级成轻量级锁,进行真正的加锁操作。这样既能够保证效率,也能够实现线程安全。

​ 偏向锁就是一个捞女,对舔狗加了微信。一直和舔狗搞暧昧,没有对象之名,却享受着舔狗的好处。拉扯距离,一直不明确关系。如果捞女玩腻了,就可以正大光明的拉黑舔狗的微信,因为本来就不是情侣关系,只是“普通朋友”。以比较高的效率完成分手的操作。如果此时,有别的捞女看上舔狗了,对此时的捞女构成了威胁。出现了“锁竞争“。之前搞暧昧的捞女,就会立刻和舔狗表白,确定情侣关系(抢先加锁)。

偏向锁 的核心思想,就是“懒汉模式” 的一种体现,能不加锁就不加锁,因为加锁就意味着开销。

2.锁消除

  • 锁消除,也是一种编译器优化的手段

​ 编译器+JVM会自动针对你自己写的加锁代码,进行判定。如果编译器认为当前的场景不需要加锁,就会把你自己写是synchronized优化掉

比如: StringBuilder ( 不带synchronized) 和 StringBuffer(带有synchronized)

如果在单个线程中使用StringBuffer,此时编译器就会自动把synchronized优化掉。

编译器只有在非常有把握的情况下,才会进行所消除。(触发概率不算很高)

相比之下,偏向锁是运行时的一种策略。锁消除是在编译阶段。

3.锁粗化

  • 同样也是编译器优化的手段
  • 一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会自动进行锁的粗化
锁的粒度

synchronized当中,代码越多,就认为锁的粒度越粗。代码越少,锁的粒度越细。

​ 锁的粒度细的时候,能够并发执行的逻辑更多,更有利于充分利用多核CPU资源。但是如果粒度细的锁,被反复进行加锁解锁,可能实际效果还不如粒度粗的锁。(涉及到反复的锁竞争)

比如领导给你安排了三个工作,完成之后,就可以打电话给领导进行汇报。于是你每完成应之后,就打电话汇报一次。但是如果你真敢这么干,领导会红温。不如一次性进行汇报。给领导打电话就是对领导进行加锁,每次进行加锁的时候,都会涉及到锁竞争。(毕竟领导也有手头工作)此时,不如只加一次锁,合在一起进行汇报。


二、信号量 Semaphore

​ 迪杰斯特拉提出的信号量,同时,他也是一位荷兰数学家,在数据结构的图中,要想获取两点之间的最短路径,就可以用迪杰斯特拉算法。 PV就是荷兰语的单词首字母。

  • 本质上就是一个计数器,描述的是“可用资源的个数”。
  • P操作:每次申请资源,让计数器-1,
  • V操作:每次释放资源,让计数器+1.(都是原子操作)

例如,停车场门口的计数牌用来显示空车位,进去一辆车,车位减一,出来一辆车,车位加一。此时可用资源就是车位。

如果可用资源为0,还要进行P操作,就会进行阻塞等待,直到其他线程执行V操作为止。

锁的本质上,就是一种可用资源为1的二元信号量。有加锁解锁两种状态。

信号量是一种广义的锁,锁是一种特殊的信号量。

  • 操作系统提供了信号量的实现,并且提供了API。JVM封装了API,就可以在Java代码中使用了。
        Semaphore semaphore = new Semaphore(3);
        semaphore.acquire();	    
		//P操作
        semaphore.release();
        //V操作

在开发中如果遇到了申请资源的场景,就可以用信号量来实现。

三、CountDownLatch

  • 同时等待N个任务执行结束

​ 适用于多个线程完成一系列任务的时候,用来衡量任务的进度是否完成。为了完成一个大的任务,把任务拆分成多个小的任务,让这些任务并发的进行执行。用CountDownLatch来判定当前的任务是否全部完成。

​ 比如IDM下载软件,能够多线程进行下载。平常的下载方式,资源和服务器只有一个连接,服务器往往会对于连接传输的速度进行限制。而多线程下载,每个线程都建立一个连接,负责其中的一部分任务。从而提高速度。

1.await方法
  • 调用的时候就会阻塞,等待其他的线程完成任务。当所有的线程都完成任务后,此时await才会返回,继续向下执行。
2.countDown方法
  • 告诉CountDownLatch,当前的一个子任务已经完成。

    每个线程完成后,调用countDown方法

    public static void main(String[] args) throws InterruptedException {
        //10个选手参赛,await会等10个人都跑完之后再继续执行
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int id = i;
            Thread thread = new Thread(() -> {
                System.out.println("thread" + id);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                countDownLatch.countDown();
                //通知当前的子任务执行完毕
            });
            thread.start();
        }
        countDownLatch.await();
        System.out.println("所有的任务结束");
thread0
thread4
thread5
thread6
thread3
thread2
thread1
thread9
thread8
thread7
所有的任务结束

点击移步博客主页,欢迎光临~

偷cyk的图

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值