文章目录
- 一、synchronized原理
- synchronized属于哪种锁?
- 1.锁升级
- 偏向锁:
- 2.锁消除
- 3.锁粗化
- 锁的粒度
- 二、信号量 Semaphore
- 三、CountDownLatch
- 1.await方法
- 2.countDown方法
一、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
所有的任务结束
点击移步博客主页,欢迎光临~