synchronized关键字用于解决多个线程之间访问资源的同步性,synchronized关键字能够保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。java
值得注意的是,在Java早期,JDK1.6以前,synchronized属于重量级锁,效率低下。git
缘由在于:编程
监视器锁【monitor】依赖于底层操做系统的
Mutex Lock
实现,Java的线程是映射到操做系统的原生线程之上的。若是要挂起或唤醒一个线程,都须要操做系统帮忙完成,而操做系统实现线程之间的切换时须要从用户态转化到内核态,须要消耗比较长的时间。数组
可是,JDK1.6以后,Java官方从JVM层面对synchronized关键字进行了较大的优化,效率不可同日而语。主要的优化有:自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减小锁操做的开销。安全
注意:静态成员不属于任何一个实例对象,是类成员!所以,一个线程A调用一个实例对象的非静态synchronized方法,一个线程B调用这个实例对象的所属类的静态synchronized方法,是被容许的。多线程
由于访问静态
synchronized
方法占用的锁是当前类的锁,而访问非静态synchronized
方法占用的锁是当前实例对象锁。并发
synchronized(this|object)
表示进入同步代码库前要得到给定对象的锁。synchronized(类.class)
表示进入同步代码前要得到 当前 class 的锁。经过对.class文件反编译能够发现:ide
ACC_SYNCHRONIZED
修饰。monitorenter
和monitorexit
两个指令实现。虽然二者实现细节不一样,但其实本质上都是JVM基于进入和退出Monitor对象来实现同步,JVM的要求以下:性能
monitorenter
指令会在编译后插入到同步代码块的开始位置,而monitorexit
则会插入到方法结束和异常处。monitor
与之关联,且当一个monitor
被持有以后,他会处于锁定状态。monitorenter
时,会尝试获取对象对应monitor
的全部权。monitorexit
时,锁计数器减一,计数为零则锁释放。https://blog.csdn.net/qq_34337272/article/details/108498442测试
优化:偏向锁,轻量级锁,自旋锁,适应性自旋锁,锁消除,锁粗化。
锁主要存在的四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁能够升级不可降级,这种策略是为了提升得到锁和释放锁的效率。
锁存在于Java对象头里,对象头的组成部分:
Java对象头又存在于Java堆中,堆内存分为三部分:对象头,实例数据和对齐填充。
Java对象头的MardWord中记录了对象和锁的相关信息,无锁状态下,Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。在64位的JVM中,Mark Word为64 bit。
在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。锁升级的功能也主要靠MarkWord中锁标志位和是否偏向锁标志完成。
锁升级的过程:无锁,偏向锁,轻量级锁,重量级锁
HotSpot的做者通过研究发现,大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。
偏向锁主要用于优化:同一线程屡次申请同一个锁的竞争,在某些状况下,大部分时间都是同一个线程竞争锁资源的。
主要流程:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
一旦出现其余线程竞争锁资源时,偏向锁就会被撤销。偏向锁的撤销可能须要等待全局安全点【在这个时间点上没有正在执行的字节码】。
偏向锁在Java 6和Java 7里是默认启用的,可是它在应用程序启动几秒钟以后才激活,若有必要可使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0
。
若是锁一般处于竞争状态,能够经过- XX:-UseBiasedLocking=false
,进入轻量级锁状态。
如偏向锁存在,若有另外一线程竞争锁,且对象头MarkWord中的线程ID与当前线程ID不一样,则该线程将会尝试CAS操做获取锁,获取失败,表明锁存在竞争,偏向锁向轻量级锁升级。
线程交替执行同步块,绝大部分的锁在整个同步周期内都不存在长时间的竞争。
锁 | 优势 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不须要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 | 若是线程间存在锁竞争, 会带来额外的锁撤销的消耗。 |
适用于只有一个线程访问同步块场景。 |
轻量级锁 | 竞争的线程不会阻塞,提升了程序的响应速度。 | 若是始终得不到锁竞争的线程使用自旋会消耗CPU。 | 追求响应时间。同步块执行速度很是快。 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU。 | 线程阻塞,响应时间缓慢。 | 追求吞吐量。同步块执行速度较长。 |
synchronized依赖于JVM实现,ReentrantLock依赖于API。
相比synchronized,ReentrantLock增长了一些高级功能。主要来讲主要有三点:
ReentrantLock
提供了一种可以中断等待锁的线程的机制,经过 lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程能够选择放弃等待,改成处理其余事情。ReentrantLock
能够指定是公平锁仍是非公平锁。而synchronized
只能是非公平锁。所谓的公平锁就是先等待的线程先得到锁。ReentrantLock
默认状况是非公平的,能够经过 ReentrantLock
类的ReentrantLock(boolean fair)
构造方法来制定是不是公平的。synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合能够实现等待/通知机制。ReentrantLock
类固然也能够实现,可是须要借助于Condition
接口与newCondition()
方法。