在并发编程中,synchronized
锁因其使用简单,在线程间同步被广泛应用。下面对其原理及锁升级过程进行探究。
当实例方法被synchronized修饰时,通过当前实例调用此方法的所有线程共用一把锁,不同对象调用此方法线程间互不影响。
public class A { public synchronized void func() { } }
当使用synchronized锁修饰实例方法,锁添加在当前类的实例上,有多少个实例可添加多少把锁。
修饰代码块比修饰方法颗粒度更小。当实例方法代码块被synchronized修饰时,通过当前实例调用此方法的所有线程共用一把锁,不同对象调用此方法线程间互不影响。
public class B { public void func() { synchronized (this) { } } }
当使用synchronized锁修饰代码块,锁添加在当前类的实例上,有多少个实例可添加多少把锁。
当静态方法被synchronized修饰时,整个JVM所有调用此方法的线程均受同一个锁的约束。
public class C { public static synchronized void func() { } }
当使用synchronized锁修饰静态方法,锁添加在当前类的类对象
上,最多添加一把锁。
非必要不使用synchronized修饰静态方法
Java 8所使用的synchronized锁是经过优化后的,存在偏向锁
、轻量级锁
、重量级锁
等状态。
线程间不存在锁的竞争行为,至多只有一个线程有获取锁的需求,常见场景为单线程程序
。
判断是不是偏向锁的标识是查看调用此方法的线程是否有且仅有一个。
在多线程编程里,被锁修饰的方法仅被单一线程调用几乎不存在,因此偏向锁比较鸡肋:如果能够明确单一线程调用目标方法,使用无锁编程更为合适。
无锁与偏向锁的性能差异非常接近,几乎可以忽略不计。
线程间存在锁的伪竞争行为
,即同一时刻绝对不会存在两个线程申请获取锁,各线程尽管都有使用锁的需求,但是是交替使用锁。
当有两个及以上线程调用被锁修饰的方法时,那么至少能确定是轻量级锁。
轻量级锁由于同一时刻不存在两个线程互相竞争锁,因此不存在线程阻塞-唤醒
的上下文切换,因此性能相对重量级锁要高很多。
线程间存在锁的实质性竞争
行为,线程间都有获取锁的需求,但是时间不可交错,互斥锁的阻塞等待。
当能够肯定至少有两个及以上线程调用被锁修饰的方法时,线程调用方法是随机的,那么大概率是重量级锁。
重量级锁由于涉及到线程阻塞-唤醒的上下文切换,造成相比较与无锁状态,效率低很多。
synchronized锁是非公平锁
,没有FIFO队列机制保障竞争锁的线程一定有几率获得锁。
synchronized锁是可重入锁
,可重入意味着嵌套调用不会产生死锁问题。
synchronized锁是一种悲观锁,通过加锁实现线程间同步。
在多线程环境下,如果使用synchronized锁,那么大概率会升级到重量级锁。偏向锁和轻量级锁非刻意为之,很难存在,更大的意义是对比帮助理解重量级锁的性能。
重量级锁尽管会对性能产生很大影响,但是依旧是解决线程间同步的有效手段。
当被锁修饰的方法或者代码块执行时间较长
时,选用基于线程阻塞-唤醒切换上下文的方式进行线程同步效率相对较高。
当被锁修饰的方法或者代码块执行时间较短
时,应选用其它替代锁,比如自旋锁等。
在实际多线程场景开发中,synchronized
锁大概率会升级到重量级锁,因其单向升级的特点,重量级状态的synchronized
锁可能会对实际业务的并发产生不利影响,手动选用其它锁可能会更合适。
synchronized
锁仅可用于解决同一进程内不同线程间同步,对于分布式项目跨进城线程同步依赖于分布式锁,synchronized
锁更多的意义是理解锁的过程。