Synchronized与Lock区别,ReentLock源码解

2018-02-13  本文已影响0人  冠冠从小爱学习

Synchronized 和Lock 的区别

Synchronized 是java 关键字,实现级别是JVM 级别;Lock 是一个接口,下面有各种实现类;
Synchronized 在方法的实现和 代码块的实现是不一样的,前者是在对象头加一个sync 标志,后者是用monitor_enter 和monitor_exit实现;
Synchronized 是阻塞,可重入,非公平锁;Lock 可重入,公平非公平实现都有;
Lock 在锁获取时可以自己决定是否阻塞,lock 与 try lock;
锁的释放:Synchronized 是由jvm 自动释放,方法执行完毕和线程发生异常;
lock 是需要由程序释放,否则容易造成死锁;

Synchronized 实现

Synchronized同时保证了可见性与原子性

Synchronized 的使用主要有三种:

  1. 同步代码块(同步某一对象)
    2.同步普通方法(同一类的不同对象持有不同的锁,只有同一对象的方法调用才会同步)
    3.同步静态方法(实际上相当于对类的同步,不管是不是该类的同一对象 都需要同步)

Synchronized是java 关键字,根据反编译结果:
对于代码块的Synchronized是通过monitorenter 和monitorexit实现的,关于其可重入的实现类似后面讲到的AQS中的state,进入+1,退出-1,monitor计数为0 代表可获取锁状态;
对于方法上Synchronized的实现,会对该方法增加ACC_SYNCHRONIZED 标识符,碰到该标识符表示访问时也需先获取monitor,本质是一样的,不过是一种隐式的实现。


Lock接口 ReentrantLock 实现:

ReentrantLock 继承自 Lock接口,Lock接口定义了如下几种方法:


lock接口方法

ReentrantLock的实现都是在其抽奖静态内部类Sync 中完成的,ReentLock本身持有Sync对象,而Sync 有两个实现类FairSync 和NonFairSync ;ReentrantLock 默认实现是NonFairSync,因其无参构造方法返回的是NonFairSync对象。Sync由继承AbstractQueuedSynchronizer 也就是传说中的AQS

1. lock()方法实现

lock实现

NonFairLock 的lock方法进来后会直接调用compareAndSetState方法获取锁,获取锁成功就会把当前线程设为锁的持有者(设为AOS[AbstractOwnableSynchronizer]中的独占线程),compareAndSetState是调用unsafe的cas方法,改变AQS中的state值。


compareAndSetState

获取锁失败就会进入acquire()方法:


acquire
首先通过tryAcquire尝试再次获得锁:
nonfair的tryAcquire实现

该方法中会先获得当前线程和当前锁状态,如果c==0,代表现在没人占有锁,直接通过compareAndSetState 进行锁抢占;如果c!=0 则判断当前持有锁的线程是否为当前线程,如果是的话state值增加所需获得锁的次数,并获得锁,这里是可重入锁的实现部分;上述都不满足条件返回false,调用acquireQueued方法,先调用addWaiter:


nonFairAddWaiter实现

addWaiter方法为当前线程生成一个Node 放到等待队列尾部。


acquireQueued

获取前置Node 如果前置节点是队列头head,则说明当前结点是第二个结点有资格去尝试获得锁(因为有可能是被head 结点释放锁后唤醒),获得锁成功则把当前节点设为头结点,当前结点出队。在finally中调用cancelAcquire方法
然后判断是否应该阻塞,主要是根据前置结点的wait_status来判断,
如果前置结点状态是SIGNAL(-1),代表前置结点在park中,当前结点可以放心阻塞;如果前置结点大于1,那么代表前置结点以推出,需要不断向前循环找到未退出的结点(<=0);如果小于0 通过CAS 将当前节点状态设为SIGNAL;如果应该阻塞并阻塞后因interrupt被唤醒 将interrupted标记位设为true。


shouldParkAfterFailedAcquire

如果应该被阻塞则调用parkAndCheckInterrupt()方法来实现阻塞,该方法调用操作系统级的park方法来使线程进入waiting状态,如果线程被唤醒 则检查是不是被interrupt的,有两种方法唤醒当前线程unpark()和interrupt(),截图中的Thread.interrupted会清空当前中断标记


阻塞实现

2.tryLock的实现

trtLock直接调用nonfairTryAcquire 直接返回获取锁结果,如果加时间的会是一个死循环尝试获取锁,每次循环检查过期时间如果过期返回false;

unlock的实现

unlock实现nonfair 和fair是一致的


unlock
release
sync中的tryrelease

nonfair和fair中的tryrelease 最终调用的都是其父类Sync中的实现,将AQS中的state减1,如果state==0了,说明当前锁释放了,清空AQS独占线程,返回true;否则返回false;
上层releas 接到返回结果如果为true,证明当前锁释放,获得当前队列中最近的一个不为null的结点(下一个等待线程)使用unpark 方法将其唤醒。

与NonFairLock的主要不同在于,lock方法直接调用Acquire方法,不会尝试直接抢占锁


fairlock

tryAcquire 与nonFairTryAcquire的不同主要在于会通过hasQueuePredecessors方法首先检查当前线程是不是等待队列中的第一个,如果是第一个(之前没有其他的等待线程),才会尝试获得锁:


fairLock的tryAcquir实现

总结

参考资料:

https://www.cnblogs.com/waterystone/p/4920797.html

上一篇 下一篇

猜你喜欢

热点阅读