synchronized
1.synchronized可以作用在方法和代码块上,方法又可以分为实例方法和静态方法,代码块可以分为锁实例对象synchronized(this),锁类对象(Demo.class),任意object对象,
String lock = "1", synchronized(lock)
实现原理:
JVM通过进入和退出monitor对象来实现的
- 方法:通过标志位ACC_SYNCHRONIZED 实现的,方法调用时,会先检查标志位是否设置,如果设置了,线程则会先持有monitor,然后执行方法,最后再释放
- 代码块:通过monitorenter和monitorexit两个字节码指令实现的,他们分别位于同步代码块的开始和结束位置,当JVM执行到monitorenter指令时会获取monitor对象的所有权,如果未加锁或者当前线程已经持有了monitor,对锁的计数器加1,执行到monitorexit时对计数器减1,当计数器为0时,锁就被释放了,而没有抢到锁的线程进入阻塞状态,直到别的线程释放了锁。
JVM对synchronized的优化
monitor是基于系统的Mutex Lock(互斥锁)实现的,而操作系统实现线程之间的切换需要用户态切换到核心态,所以成本高,耗时,也就是我们说的重量级锁。
- jdk1.6的锁优化,引入了偏向锁和轻量级锁,锁的排序依次是:无锁状态,偏向锁,轻量级锁,重量级锁,锁可以升级,不能降级
1.1:偏向锁:当锁对象第一次被获取的时候,虚拟机会把对象的标志位设置为01(即偏向模式),并把线程ID记录到对象的Mark work中(这一步基于CAS操作),成功后,每次这个线程进入这个锁相关的同步块的时候,虚拟机不会在进行同步操作
1.2:轻量级锁:CAS
1.3:重量级锁:基于monitor实现
ReentrantLock(基于AQS实现)
在操作前加锁,finally中解锁
ReentrantLock默认创建非公平锁,加参数true创建公平锁
- 非公平锁实现:加入线程1调用lock方法,会将AbstractQueuedSynchronizer的state设置为1,AbstractQueuedSynchronizer的thread设置为当前线程,这两步做完线程1相当于独占了锁,此时线程2抢占锁,肯定无法成功,所以线程2会被阻塞,
阻塞是怎么实现的:
1.1. 首先会判断state是否为0,此时线程1已经设置为1,所以会失败走else中的acquire方法
1.2. acquire方法中重要有两个方法tryAcquire和acquireQueued,会先尝试获取锁(可能线程1执行较快,已经释放了锁),获取状态是否为0,如果不是0,判断是否为当前线程,是则state+1,失败以后走两个判断条件,创建FIFO等待队列(enq方法实现的)
1.3. acquireQueued:对于头结点后的第一个节点,首先尝试获取锁,失败后调用shouldParkAfterFailedAcquire方法,将head的waitState设置为Signal,最后调用LockSupport.park()方法阻塞当前线程
2.锁释放:锁释放成功后唤醒挂起的线程,将head节点最近节点设置为head节点(释放掉head)