synchronized原理

目录

1.基本特点

2.锁升级/锁膨胀

2.1 偏向锁

2.2 轻量级锁

2.3 重量级锁

3.锁消除

4.锁粗化


1.基本特点

       结合我们前面所说的锁策略,我们可以总结出synchronized具有以下特性(在JDK1.8中):

1. 初始阶段采用乐观锁策略,如果锁冲突频繁发生,则切换为悲观锁。

2. 初始阶段使用轻量级锁实现,但如果锁持有时间较长,将转换为重量级锁。

3. 在实现轻量级锁时,通常会采用自旋锁策略。

4. 它是一种不公平锁,可能导致某些线程长时间等待获取锁。

5. 它是一种可重入锁,允许同一个线程多次获取同一把锁。

6. 它不是读写锁,读写锁允许多个线程同时读取共享资源,但只允许一个线程写入。

2.锁升级/锁膨胀

       JVM将synchronized锁分为无锁,偏向锁,轻量级锁,重量级锁,会根据情况,进行依次升级。

2.1 偏向锁

       在synchronized加锁时,首先会进入偏向锁状态。偏向锁并不是真正的加锁,而只是占据一个位置,只有在需要时才会进行加锁。偏向锁的过程类似于懒汉模式中的懒加载,即“非必要不进行加锁”

       如果在synchronized执行过程中没有出现锁竞争,那么在执行完成后,可以取消偏向锁。然而,如果在使用过程中,另一个线程也尝试加锁,那么在其加锁之前,偏向锁会迅速升级为真正的加锁状态,此时另一个线程将处于阻塞等待状态。

举个例子:

       当你进入一个房间时,你可能会先检查门是否已经上锁。如果门没有上锁,你可以自由进入房间,而不需要额外的操作。这就是偏向锁的概念,它只是占据一个位置,只有在需要时才会进行加锁。

       然而,如果在你进入房间的过程中,另一个人也尝试进入同一个房间,并且他发现门没有上锁,那么他会迅速将门锁上,以确保房间的安全。这时,你就不得不等待他完成锁门的操作,然后才能进入房间。这就是偏向锁升级为真正的加锁状态的情况,此时另一个线程会处于阻塞等待状态。

2.2 轻量级锁

       当synchronized发生锁竞争时,偏向锁会升级为轻量级锁。在轻量级锁状态下,synchronized通过自旋的方式进行加锁。自旋是一种尝试获取锁的方式,它会不断检查锁是否可用,而不阻塞线程。这种方式对于锁持有时间较短的情况非常有效。

       然而,如果锁长时间被占用,自旋会导致大量的CPU资源消耗。为了避免这种情况,synchronized的自旋并不是无休止的。它有一个计数器来记录自旋的次数,当自旋次数达到一定阈值后,锁就会升级为重量级锁。重量级锁是一种更复杂的锁机制,它会阻塞线程直到锁被释放。

       总之,synchronized通过自旋和计数器的组合来实现高效的锁管理,既避免了长时间的阻塞,又减少了不必要的CPU资源消耗。

2.3 重量级锁

       重量级锁(等待挂起锁)是一种基于操作系统API的加锁机制。在Linux系统中,原生提供了mutex一组API来实现加锁功能。然而,操作系统提供的加锁功能会对线程调度产生影响。当一个线程尝试获取重量级锁时,如果锁已经被其他线程持有,该线程将被放入阻塞队列中,暂时不参与CPU的调度。只有当锁被释放后,这个线程才有机会被调度到并有机会获得锁。

       这种机制确保了在同一时刻只有一个线程能够访问共享资源,从而避免了并发问题。然而,由于线程需要等待锁的释放,这可能会导致线程的执行效率降低,尤其是在高并发场景下。因此,在使用重量级锁时,需要权衡性能和并发控制的需求。

3.锁消除

       编译器智能判定是一种优化技术,它能够根据代码的上下文和运行时环境来判断是否需要真正进行加锁。在某些情况下,即使使用了synchronized关键字,编译器也会自动移除不必要的锁操作。

  例如,在单线程场景下使用StringBuffer时,由于只有一个线程访问共享资源,因此不需要额外的同步措施。在这种情况下,编译器会自动识别出这种场景,并省略掉synchronized关键字所表示的锁操作。

       这种编译器优化可以减少不必要的性能开销,提高程序的运行效率。然而,需要注意的是,这种优化是基于编译器对代码的分析,并不能保证在所有情况下都能正确工作。因此,在使用synchronized关键字时,仍然需要谨慎考虑并发控制的需求,以确保程序的正确性和安全性。

4.锁粗化

       锁的粒度是指加锁保护的范围大小。synchronized关键字所包含的代码越多,锁的粒度就越粗;反之,包含代码越少,锁的粒度就越细。

       当一个线程持有锁时,其他线程无法访问被锁定的代码区域,直到锁被释放。因此,锁的粒度越细,能够并发执行的代码就越多;反之,锁的粒度越粗,能够并发执行的代码就越少。

       然而,在某些情况下,锁的粒度粗一点可能更好。

  例如,如果两次加锁解锁之间的时间间隔非常短,那么频繁地获取和释放锁可能会带来额外的开销。在这种情况下,一次加一个大锁可能更加高效。

       需要注意的是,锁的粒度的选择应该根据具体的应用场景和需求来决定。在设计并发程序时,需要权衡性能、资源利用率和正确性等因素,以找到最佳的锁粒度。

你可能感兴趣的:(JavaEE,java,开发语言)