目录

synchronized详解

synchronized简介

保证方法或代码块在多线程环境运行时,同一个时刻只有一个线程执行代码块。

  • JDK1.6之前,synchronized的实现依赖于OS底层互斥锁的MutexLock,存在严重的性能问题
  • JDK1.6之后,Java对synchronized进行的了一系列优化,实现方式也改为Monitor(管程)了
  • 一句话:有了Synchronized,就线程安全了,保证原子性、可见性、有序性

可以修饰方法(静态和非静态)和代码块

  • 同步代码块的锁:当前对象,字节码对象,其他对象
  • 非静态同步方法:锁当前对象
  • 静态同步方法:锁是当前类的Class对象

synchronized原理剖析

如何解决可见性问题?Happens-before规则

JMM对于Synchronized的规定:

  • 加锁前:必须把自己本地内存中共享变量的最新值刷到主内存
  • 加锁时:清空本地内存中的共享变量,从主内存中读取共享变量最新的值

Synchronized是如何实现同步的呢

同步操作主要是monitorenter和monitorexit两个jvm指令实现。背后原理是Monitor(管程)

什么是Monitor

  • Monitor意译为管程,直译为监视器。所谓管程,就是理共享变量及对共享变量操作的过。让这个过程可以并发执行。
  • Java所有对象都可以做为锁,为什么?
  • 因为每个对象都都有一个Monitor与之关联。然后线程对monitor执行lock和unlock操作,相当于对对象执行上锁和解锁操作。
  • Synchronized里面不可以直接使用lock和unlock方法,但当我们使用了synchronized之后,JVM会自动加入两个指令monitorenter和monitorexit,对应的就是lock和unlock操作。
  • 当一个monitor对象被线程持有后,它将处于锁定状态。对于一个 monitor 而言,同时只能有一个线程能锁定monitor,其它线程试图获得已被锁定的 monitor时,都将被阻塞。当monitor被释放后,阻塞中的线程会尝试获得该 monitor锁。一个线程可以对一个 monitor 反复执行 lock 操作,对应的释放锁时,需要执行相同次数的 unlock 操作。

Monitor如何解决了线程安全问题?

管程解决互斥问题的思路:就是将共享变量及其对共享变量的操作统一封装起来。

/images/current/JCP-monitor.png
管程

为什么所有对象都可以作为锁?

因为每个对象都都有一个Monitor对象与之关联。然后线程对monitor执行lock和unlock操作,相当于对对象执行上锁和解锁操作。

什么是锁优化

  • 在JDK 1.6之前,synchronized使用传统的锁(重量级锁)实现。它依赖于操作系统(互斥量)的同步机制,涉及到用户态和内核态的切换、线程的上下文切换,性能开销较高,所以给开发者留下了synchronized关键字性能不好的印象。
  • 如果只有一个线程运行时并没有发生资源竞争、或两个线程交替执行,使用传统锁机制无疑效率是会比较低的。
  • JDK1.6中为了减少这两个场景,获得锁和释放锁带来的性能消耗,同步锁进行优化引入:偏向锁和轻量级锁
  • 同步锁一共有四种状态,级别从低到高依次是:无锁,偏向锁,轻量级锁,重量级锁。这四种状态会随着竞争激烈情况逐渐升级。
  • 偏向锁则是基这样一个想法:只有一个线程访问锁资源(无竞争)的话,偏向锁就会把整个同步措施都消除,并记录当前持有锁资源的线程和锁的类型。
  • 轻量级锁是基于这样一个想法只有两个线程交替运行时,如果线程竞争锁失败了,先不立即挂起,而是让它飞一会儿(自旋),在等待过程中,可能锁就被释放了,这时该线程就可以重新尝试获取锁,同时记录持有锁资源的线程和锁的类型。

锁升级(无锁→偏向锁→轻量级锁→重量级锁)

在Java中,synchronized共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级,锁可以升级但不能降级。

  • 偏向锁:是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
  • 轻量级锁:(自旋锁)是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程同样不会阻塞。长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)
  • 重量级锁:此忙等是有限度的。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。有个计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改

参考文章

synchronized详解 不可不说的Java“锁”事