多线程高并发之Synchronized锁及其膨胀

多线程高并发之Synchronized锁及其膨胀,第1张

在并发编程中,synchronized锁因其使用简单,在线程间同步被广泛应用。下面对其原理及锁升级过程进行探究。

当实例方法被synchronized修饰时,通过当前实例调用此方法的所有线程共用一把锁,不同对象调用此方法线程间互不影响。

当使用synchronized锁修饰实例方法,锁添加在当前类的实例上,有多少个实例可添加多少把锁。

修饰代脊喊码块比修饰方法颗粒度更小。当实例方法代码块被synchronized修饰时,通过当前实例调用此方法的所有线程共用一把锁,不同对象调用此方法线程间互不影响。

当使用synchronized锁修饰代码块,锁添加在当前类的实例上,有多少个实例可添加多少把锁。

当静态方法被synchronized修饰时,整个JVM所有调用此方法的线程均受同一个锁的约束。

当使用synchronized锁修饰静态方法,锁添加在当前类的类对象上,最多添加一把锁。

Java 8所使用的synchronized锁是经过优化后的,存在偏向团野隐锁、轻量级锁、重量级锁等状态。

线程间不存在锁的竞争行为,至多只有一个线程有获取锁的需求,常见场景为单线程程序。

判断是不是偏向锁的标识是查看调用此方法的线程是否有且仅有一个。

在多线程编程里,被锁修饰的方法仅被单一线程调用几乎不存在,因此偏向锁比较鸡肋:如果能够明确单一线程调用目标方法,使用无锁编程更为合适。

无锁与偏向锁的性能差异非常接近,几乎可以忽略不计。

线程间存在锁的伪竞争行为,即同一时刻绝对不会存在两个线程申请获取锁,各线程尽管都有使用锁的需求,但是是交替使用锁。

当有两个及以上线程调用被锁修饰的方法时,那么至少能确定是轻量级锁。

轻量级锁由于同一时刻不存在两个线程互相竞争锁,因此不存在线程阻塞-唤醒的上下文切换,因此性能相对重塌厅量级锁要高很多。

线程间存在锁的实质性竞争行为,线程间都有获取锁的需求,但是时间不可交错,互斥锁的阻塞等待。

当能够肯定至少有两个及以上线程调用被锁修饰的方法时,线程调用方法是随机的,那么大概率是重量级锁。

重量级锁由于涉及到线程阻塞-唤醒的上下文切换,造成相比较与无锁状态,效率低很多。

synchronized锁是非公平锁,没有FIFO队列机制保障竞争锁的线程一定有几率获得锁。

synchronized锁是可重入锁,可重入意味着嵌套调用不会产生死锁问题。

synchronized锁是一种悲观锁,通过加锁实现线程间同步。

在多线程环境下,如果使用synchronized锁,那么大概率会升级到重量级锁。偏向锁和轻量级锁非刻意为之,很难存在,更大的意义是对比帮助理解重量级锁的性能。

重量级锁尽管会对性能产生很大影响,但是依旧是解决线程间同步的有效手段。

当被锁修饰的方法或者代码块执行时间较长时,选用基于线程阻塞-唤醒切换上下文的方式进行线程同步效率相对较高。

当被锁修饰的方法或者代码块执行时间较短时,应选用其它替代锁,比如自旋锁等。

在实际多线程场景开发中,synchronized锁大概率会升级到重量级锁,因其单向升级的特点,重量级状态的synchronized锁可能会对实际业务的并发产生不利影响,手动选用其它锁可能会更合适。

synchronized锁仅可用于解决同一进程内不同线程间同步,对于分布式项目跨进城线程同步依赖于分布式锁,synchronized锁更多的意义是理解锁的过程。

synchronized 底层如何实现?什么是锁的升级,降级。

*** 作系统分为用户态和内核态,应用级别的程序会运行在用户态,不能访问硬件, *** 作册闷系统内核的程序会运行在内核态,可以直接访问硬件。synchronized 是重量级锁,运行在虚拟机上,而虚拟机是应用级别的程序,运行在用户态,需要通过向 *** 作系统内核程序发出申请,得到反馈获得锁,所以称sychronized为重量级锁。而cas的锁直接运行在用户态,所以称为轻量级锁。

CAS 叫自旋锁或者无锁,是轻量级锁,用于替代synchronized。

CAS的ABA问题可以用版本号解决。可是如果一个线程在比较值相同的情况下,在修改值之前另一个线程有可能提前修改当前值,这怎么避免呢?如下图:

在碧森用c++编写的native方法compareAndSwap中,如果多线程的情况下,会有 lock cmpxchg这条指令来保证线程安全。在底层有多个cpu指向同一条语句(CAS)时,多个cpu通过一条总线通向这条语句,当lock时,会掐断这条总线,直到通向这条语句的cpu执行完成,总线又连上,允许其他cpu *** 作这条语句。

Markword的前四个字节中记录了synchronized的锁信息。

轻量级锁也称为自旋锁。除了重量级锁,其他锁都是在用户态完成。

锁升级指的是轻量级锁升级成重量级锁。

为什么自旋锁可以完成多线程的安全,为什么在竞争激烈的情况下要升级成重量级锁,因为自旋锁这些线程一直在while循环,消耗cpu的资源,而重量级锁这些线程排成队列,不消耗cpu资源,这里又可以分为公平锁和非公平锁。

偏向锁是默认启动的,但是有4s的延迟。

启动偏向锁为什么要延迟4秒:因为启动偏向锁效率不一定会提升。如果一开始就知道会有很多线程竞争锁,那么就不必打开偏州慧弯向锁,从而提高效率。

Synchronized的底层实现:

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:

从语法上讲,Synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中, 锁有个专门的名字:对象监视器(Object Monitor)

Synchronized总共有三种用法:

注意,synchronized 内置锁 是一种对象锁(锁的是对象而非引用变量), 作用粒度是对象 ,可以用来实现对临界资源的同步互斥访问,是 可重入 的。其可重入最大的作用是避免死锁 ,如:

子类同步方法调用了父类同步方法,如没有可重入的特性,则会发生死锁;

monitorenter :每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

(1). 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;

(2). 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;

(3). 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;

通过上面两段描述,我们应该能很清楚的看出Synchronized的实现原理, Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

修饰方法就代表锁的是此方法体,如 public synchronized int cal(){...} 表示每次访问此方法都只能一个线程访问,其他的要等待访问完了才能进入此方法,冲梁这是竞争锁,synchronized(obj)锁的是obj,代表腊正只有获取了此obj锁,才能继续访问,更高级的推散局运荐使用Lock或ReentrainLock。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/12119588.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-21
下一篇 2023-05-21

发表评论

登录后才能评论

评论列表(0条)

保存