1、作用:
(1)确保线程互斥的访问同步代码 (2)保证共享变量的修改能够及时可见 (3)有效解决重排序问题。
2、用法:
1、修饰实例方法 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。 2、修饰静态方法 作用于类的所有对象的锁。 如果线程A调用实例对象的非静态Synchronized方法,而线程B调用这个实例对象所属类的静态Synchronized方法,是允许的,不会发生互斥现象, 因为访问静态Synchronized方法是占用的锁是当前类的锁,而访问非静态Synchronized方法是占用当前实例对象的锁。 3、修饰代码块 指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。
3、为什么说Synchronized 是非公平锁?
非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。
4、Synchronized底层原理
① Synchronized修饰语句块 使用的是monitorenter和monitorexit标识同步代码块的起始,线程获取锁就是获取monitorenter的持有权,当计数器为0成功获取锁,计数器加一,当执行monitorexit后计数器减一,其他线程可以正常获取锁。 ② Synchronized修饰方法 使用ACC_SYNCHRONIZED标识,标识指明该方法是一个同步方法,JVM通过标识判断此方法是否声明的是同步方法,从而执行相应的同步调用。
Java对象头是synchronized实现的关键,Synchronized用的锁是存在Java对象头中
其中 Mark Word 在默认情况下存储着对象的 HashCode、分代年龄、锁标记位等。Mark Word在不同的锁状态下存储的内容不同,在32位JVM中默认状态为下:
5、同步过程(锁升级过程)
synchronized 在开始的时候是依靠操作系统的互斥锁来实现的,是个重量级操作,为了减少获得锁和释放锁带来的性能消耗,在 JDK 1.6中,引入了偏向锁和轻量级锁。锁一共有4中状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几种状态会随着竞争情况逐渐升级,但不能降级,目的是为了提高锁和释放锁的效率。 5.1、偏向锁 大部分情况下,锁不存在多线程竞争,偏向锁就是为了在只有一个线程执行同步块时提高性能。 偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。 5.2、轻量级锁 轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,轻量级锁并不是用来代替重量级锁的,它是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。 5.3、重量级锁
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。
轻量级锁是为了在线程交替执行同步块时提高性能(通过自旋锁,使无法获得锁的线程无需立即进入阻塞状态,而是在一定时间内循环以获得锁,减少挂起线程和恢复线程带来的消耗)
而偏向锁则是在只有一个线程执行同步块时进一步提高性能(在无多线程竞争情况下,获得锁的线程不释放锁,以减少CAS操作)
偏向锁获取过程
偏向锁的释放
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。 偏向锁的撤销,需要等待全局安全点(这个时间点没有正在执行的字节码)。
偏向锁“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
轻量级锁的加锁过程
锁释放过程:
轻量级锁膨胀为重量级锁,Mark Word的锁标记位更新为10,Mark Word 指向互斥量(重量级锁)。
Synchronized 的重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。