所谓显式锁和隐式锁,主要指的就是 synchronized 关键字和 ReentrantLock 类。
下面具体聊一聊二者之间的区别:
1 底层不同
synchronized 是java中的关键字,是JVM层面的锁。
ReentrantLock 是是JDK5以后出现的具体的类。使用lock是调用对应的API。
我们通过Javap命令来查看调用二者的汇编指令:
可以看出 synchronized 是底层是通过monitorenter进行加锁,通过monitorexit来退出锁的。
ReentrantLock 对象是通过调用对应的API方法来获取锁和释放锁的。
2 使用方式不同
正如文章的标题那样,显式锁和隐式锁最大的不同就是在使用的时候,程序员要不要手动写代码去获取锁和释放锁的操作。
在使用 synchronized 关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当synchronized 代码块执行完成之后,系统会自动的让程序释放占用的锁。 如下:
public class Demo9 {
public static void main(String[] args) {
//线程不安全
//解决方案2 同步方法
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket implements Runnable{
//总票数
private int count = 10;
@Override
public void run() {
while (true) {
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
return true;
}
return false;
}
}
}
在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。
lock.lock();
lock.unlock();
举例如下:
public class Demo10 {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//总票数
private int count = 10;
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
}else {
break;
}
l.unlock();
}
}
}
}
3 是否可以中断
synchronized 是不可中断的。除非抛出异常或者正常运行完成
lock可以中断的。中断方式:
- 调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
4 是否是公平锁
synchronized 是非公平锁
lock创建时可以传入一个Boolean值来设置是公平锁还是非公平锁:
5 性能
在java1.6之前synchronized性能较低,因为其需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还要多,相比之下lock的性能更高一些。
然而到了java1.6之后对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁导致synchronized的性能也不差,并且由于其在语义上很清晰所以也被官方更多的支持。
6 使用锁的方式
lock 获取锁与释放锁的过程,都需要程序员手动的控制 Lock用的是乐观锁方式。
- 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
synchronized托管给jvm执行 原始采用的是CPU悲观锁机制
- 悲观锁:线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。