Synchronized 和Lock 的区别
Synchronized 是java 关键字,实现级别是JVM 级别;Lock 是一个接口,下面有各种实现类;
Synchronized 在方法的实现和 代码块的实现是不一样的,前者是在对象头加一个sync 标志,后者是用monitor_enter 和monitor_exit实现;
Synchronized 是阻塞,可重入,非公平锁;Lock 可重入,公平非公平实现都有;
Lock 在锁获取时可以自己决定是否阻塞,lock 与 try lock;
锁的释放:Synchronized 是由jvm 自动释放,方法执行完毕和线程发生异常;
lock 是需要由程序释放,否则容易造成死锁;
Synchronized 实现
Synchronized同时保证了可见性与原子性
-
Synchronized的使用方法
Synchronized 的使用主要有三种:
- 同步代码块(同步某一对象)
2.同步普通方法(同一类的不同对象持有不同的锁,只有同一对象的方法调用才会同步)
3.同步静态方法(实际上相当于对类的同步,不管是不是该类的同一对象 都需要同步)
-
Synchronized 原理
Synchronized是java 关键字,根据反编译结果:
对于代码块的Synchronized是通过monitorenter 和monitorexit实现的,关于其可重入的实现类似后面讲到的AQS中的state,进入+1,退出-1,monitor计数为0 代表可获取锁状态;
对于方法上Synchronized的实现,会对该方法增加ACC_SYNCHRONIZED 标识符,碰到该标识符表示访问时也需先获取monitor,本质是一样的,不过是一种隐式的实现。
Lock接口 ReentrantLock 实现:
ReentrantLock 继承自 Lock接口,Lock接口定义了如下几种方法:
ReentrantLock的实现都是在其抽奖静态内部类Sync 中完成的,ReentLock本身持有Sync对象,而Sync 有两个实现类FairSync 和NonFairSync ;ReentrantLock 默认实现是NonFairSync,因其无参构造方法返回的是NonFairSync对象。Sync由继承AbstractQueuedSynchronizer 也就是传说中的AQS
-
NonFairLock的实现
1. lock()方法实现
NonFairLock 的lock方法进来后会直接调用compareAndSetState方法获取锁,获取锁成功就会把当前线程设为锁的持有者(设为AOS[AbstractOwnableSynchronizer]中的独占线程),compareAndSetState是调用unsafe的cas方法,改变AQS中的state值。
获取锁失败就会进入acquire()方法:
首先通过tryAcquire尝试再次获得锁:
该方法中会先获得当前线程和当前锁状态,如果c==0,代表现在没人占有锁,直接通过compareAndSetState 进行锁抢占;如果c!=0 则判断当前持有锁的线程是否为当前线程,如果是的话state值增加所需获得锁的次数,并获得锁,这里是可重入锁的实现部分;上述都不满足条件返回false,调用acquireQueued方法,先调用addWaiter:
addWaiter方法为当前线程生成一个Node 放到等待队列尾部。
获取前置Node 如果前置节点是队列头head,则说明当前结点是第二个结点有资格去尝试获得锁(因为有可能是被head 结点释放锁后唤醒),获得锁成功则把当前节点设为头结点,当前结点出队。在finally中调用cancelAcquire方法
然后判断是否应该阻塞,主要是根据前置结点的wait_status来判断,
如果前置结点状态是SIGNAL(-1),代表前置结点在park中,当前结点可以放心阻塞;如果前置结点大于1,那么代表前置结点以推出,需要不断向前循环找到未退出的结点(<=0);如果小于0 通过CAS 将当前节点状态设为SIGNAL;如果应该阻塞并阻塞后因interrupt被唤醒 将interrupted标记位设为true。
如果应该被阻塞则调用parkAndCheckInterrupt()方法来实现阻塞,该方法调用操作系统级的park方法来使线程进入waiting状态,如果线程被唤醒 则检查是不是被interrupt的,有两种方法唤醒当前线程unpark()和interrupt(),截图中的Thread.interrupted会清空当前中断标记
2.tryLock的实现
trtLock直接调用nonfairTryAcquire 直接返回获取锁结果,如果加时间的会是一个死循环尝试获取锁,每次循环检查过期时间如果过期返回false;
unlock的实现
unlock实现nonfair 和fair是一致的
nonfair和fair中的tryrelease 最终调用的都是其父类Sync中的实现,将AQS中的state减1,如果state==0了,说明当前锁释放了,清空AQS独占线程,返回true;否则返回false;
上层releas 接到返回结果如果为true,证明当前锁释放,获得当前队列中最近的一个不为null的结点(下一个等待线程)使用unpark 方法将其唤醒。
-
FairLock的实现
与NonFairLock的主要不同在于,lock方法直接调用Acquire方法,不会尝试直接抢占锁
tryAcquire 与nonFairTryAcquire的不同主要在于会通过hasQueuePredecessors方法首先检查当前线程是不是等待队列中的第一个,如果是第一个(之前没有其他的等待线程),才会尝试获得锁:
总结
- NonFair 和Fair的unlock实现一致,lock方法的主要区别是nonFair会第一时间尝试抢占锁,fair会先检查等待队列,在没有前置的等待线程后才会尝试获得锁。通过AQS中的state的增加和exclusiveOwnerThread 的判断实现了可重入;通过操作系统的park方法实现了则色;通过了死循环 和 interrupt,unpark 唤醒机制 实现了自旋(spinlock)。
- AQS(AbstractQueuedSynchronizer)该类继承顶级抽象类AOS(AbstractOwnableSynchronizer),AOS中主要维护了独占线程,来表示是哪个线程当前持有锁;AQS中比较重要的一个参数是volatile修饰的state,该参数为0 时代表当前锁是可获取的;AQS中还有一个重要的静态内部类Node,Node是等待线程队列中的具体对象,一个Node 代表一个线程,维护了其前置Node 和next node,并有status 表示当前线程所处的状态是否在沉睡,这几个状态都是由volatile修饰的;
- AQS 是一个锁框架,它维护了等待锁线程队列,包括入队,唤醒线程,判断是否该沉睡等一些列操作,自定义的同步器(例如NonFairSync 和 FairSync)只需实现自定义的lock 和 unlock 就好~
第一次看这块源码还有一些地方没太看懂,欢迎指正~