Synchronized同步锁
Synchronized同步锁
JAVA锁
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低, 每次去拿数据的时候都认为别人不会修改,所以不会上锁。 但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作。
悲观锁
悲观锁是就是悲观思想,与乐观锁相反,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以 每次在读写数据的时候都会上锁 ,这样别人想读写这个数据就会 block 直到拿到锁。
自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就 避免用户线程和内核的切换的消耗 。说白了就是等待锁的线程不会立即阻塞,会自旋一段时间,等待其他线程释放锁。
关于自旋锁的自旋时间:因为本身自旋就是做无用功,如果设置自旋时间过长同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗。所以 JDK1.6中引入了适应性自旋锁 。
Synchronized分析
他属于独占式的悲观锁,同时属于可重入锁。
Synchronized上锁
锁方法
Synchronized作用于对象方法上时,锁的是对象实例(this),也就是同时只能有一个线程在调用该实例方法,其他使用该对象方法的线程将会被阻塞。体会一下下面代码就知道锁的是对象了。当锁住this时,多个线程只有一个线程能跑Synchronized的方法。
static int num = 100;
public synchronized void synfunc(){
int i = 0;
while (i<3){
num--;i++;
System.out.println(Thread.currentThread().getName()+"------"+num);
}
}
public void nosynfunc(){
int i = 0;
//这个可以调大,明显一点
while (i<15){
i++;
System.out.println(Thread.currentThread().getName()+"运行非syn方法");
}
}
public static void main(String[] args) {
test3 test = new test3();
new Thread(()->{test.synfunc();}).start(); //1
new Thread(()->{test.nosynfunc();}).start(); //2
new Thread(()->{test.synfunc();}).start(); //3
new Thread(()->{test.nosynfunc();}).start(); //4
new Thread(()->{test.synfunc();}).start(); //5
}
/*
结果:
自己多运行几次就知道了,1,3,5绝对是有序的,(肯定是先1->3->5)
而2,4这两个非同步方法是无序的,可以出现在任何地方
*/
锁静态方法
当Synchronized作用于静态方法时,锁住的是Class实例。静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。(不演示了,我懒)
锁代码块
synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。(不演示了,我懒)
Synchronized核心组件
- Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;
- Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
- Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;
- OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;
- Owner:当前已经获取到所资源的线程被称为 Owner;
CSA初识
CAS机制就是对乐观锁的一种实现。CAS,全称是Compare And Swap,即比较并交换,是一种乐观锁的实现。 这里不介绍CSA,了解即可。
Synchronized实现
参考博文:https://blog.csdn.net/zqz_zqz/article/details/70233767
- JVM 每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList 会被大量的并发线程进行 CAS 访问,为了降低对尾部元素的竞争,JVM 会将一部分线程移动到 EntryList 中作为候选竞争线程。
- Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定EntryList 中的某个线程为 OnDeck 线程(一般是最先进去的那个线程)。
- Owner 线程并不直接把锁传递给 OnDeck 线程,而是把锁竞争的权利交给 OnDeck,OnDeck 需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM 中,也把这种选择行为称之为“竞争切换”。
- OnDeck 线程获取到锁资源后会变为 Owner 线程,而没有得到锁资源的仍然停留在 EntryList中。如果 Owner 线程被 wait 方法阻塞,则转移到 WaitSet 队列中,直到某个时刻通过 notify或者 notifyAll 唤醒,会重新进去 EntryList 中。
- 处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux 内核下采用 pthread_mutex_lock 内核函数实现的)。
- Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,等待的线程会先尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源。
java线程阻塞的代价
java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换 ,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。
- 如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间;
- 如果对于那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然非常糟糕的。
synchronized会导致争用不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁,为了缓解上述性能问题,JVM从1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。
原文链接:https://blog.csdn.net/zqz_zqz/article/details/70233767
JVM从1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。
原文链接:https://blog.csdn.net/zqz_zqz/article/details/70233767
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)