上次通过ReentrantLock来解析AQS,这次我们来详细讲讲ReentrantLock和Synchorized的异同吧。
大多数人在需要将一个方法变成线程安全时,第一个想到的就是加上Synchorized关键字,确实通过加Synchorized的方式是很简单的,开发者不需要再关心锁的释放,程序跑就行了,但正是这么简单的方式会带来很多的问题,记住,越是简单的东西,其带来的扩展性是很低的,小伙伴们要慎用哦,接下来就分别来看看ReentrantLock和Synchorized的使用方式和优劣吧。
首先我们先来比较一下两个的异同。
1.内部实现锁的方式
下图为Synchornized的锁升级机制,图来自于网络,可以了解一下
Synchorized: 加锁是通过底层的JVM对象指令,争夺的是moniter对象,通过对象头的信息来判断改对象是否加锁并且线程持有的threadId,同时在Synchorized加锁过程中,多个线程争抢时还会发生锁的升级机制,大概过程为:无锁–>偏向锁–>轻量级锁–>重量级锁
ReentrantLock:这个加锁过程在上篇文章中已经将的很清楚了,通过cas机制来判断锁是否有线程持有,如果没有就将state由0改为1并设置锁的持有线程为当前线程,同时还有可重入机制。
2.锁的释放
Synchorized:用户不可以手动去释放,只有加锁的代码块运行完成或者出现异常,锁才会自动释放,这样多个线程并发会造成死锁问题。关于死锁,后续我也会详细讲解。
ReentrantLock:通过lock()手动加锁,unLock()手动释放锁,方式更加灵活。
3.锁是否可以中断
Synchorized:不可中端锁,除非加锁代码块发生异常或者运行成功。
ReentrantLock:在上篇文中只讲解了lock()方法,在ReentrantLock中有tryLock()方法,可以传入获取锁的时间,当超过传入时间时,会将线程挂起加入等待队列,等待再次被唤醒。
4.是否是公平锁
Synchorized:非公平锁,多个线程都有获取到锁的机会,不区分线程的先来后到。
ReentrantLock:这个上篇也讲到了,在ReentrantLock默认不带参数初始化时,是创建的非公平锁,new ReentrantLock(boolean fair) 可以用来指定新建锁是否是公平的。
tips: 关于上篇中,有个思考,ReentrantLock怎么来实现公平锁和公平锁的,这里就来解答啦,直接上源码就很清晰了,go
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
}
区别就在于NonfairSync()(非公平)中多了个compareAndSetState(0, 1)方法,这个很关键,在非公平的时候,有这样一种情况,当锁释放之后,这是有新创建的线程也来正争抢锁,这是和等待队列的线程一起发生的,这个时候就破坏了先来后到的机制了。在公平锁的时候,每次新建线程都要当前线程等待获取锁,所以后来的锁一定是后获取到锁的。
注意:在等待队列里面的线程,是一定遵循先来的先获取到锁,后来的后获取到到锁,不管是公平锁还是非公平锁都是如此,上面是的不遵循的情况指的是当前锁还没有进入到等待队列的情况,它会和等待队列里面的线程一起争抢锁哦。 其实上面也解释了我上篇文章中的疑惑,为什么state==0这个状态要重复判断两次的问题。
5.锁是否可以绑定条件Condition
Synchorized:锁的睡眠和唤醒是通过在有Synchorized代码块中的notify()和wait()方法来实现的,这个唤醒是随机的并不能精准唤醒某一个线程。
ReentrantLock:通过用创建的锁来绑定不同的Condition对象,可以达到精准唤醒的目的,具体方法为await()和signal()方法。
6.锁的目标
Synchorized:锁的是对象,通过对象头中的锁的表示来判断当前是否有线程获取到锁。
ReentrantLock:锁的是线程,通过源码可以看到,通过判断进入的线程和当前锁设置的线程,已经state的值来说明当前线程能否获取到锁。
以上就是ReentrantLock和Synchorized的区别了,小伙伴们可以在日常开发中,合适的选择使用。