2020-07-05

显示锁和隐式锁

定义

隐式锁(Synchronized)是Java的关键字,当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只有一个线程执行该代码。因为当调用Synchronized修饰的代码时,并不需要显示的加锁和解锁的过程,所以称之为隐式锁。

显示锁(Lock)是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁。

区别

1.出身不同

Synchronized:Java中的关键字,是由IVM来维护的,是JVM层面的锁。

Synchronized的获取锁和释放锁是在JVM内部实现的,他就相当于给当前线程做了一个标记,由JVM内部去识别,并做出相应反应。

Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁。

lock是通过调用对应的API方法来获取锁和释放锁的。

sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。

而lock是通过调用对应的API方法来获取锁和释放锁的。

我们通过Javap命令来查看调用sync和lock的汇编指令:

2.使用方法不同

Synchronized是隐式锁。Lock是显示锁

所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。

在使用Synchronized关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当Synchronized代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话,是不会出现死锁的。

在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。

//隐式锁

synchronized(锁对象){

//任务代码

}

//显示锁

    Lockl=newReentrantLock()

@Override

publicvoidrun(){

    l.lock;

//任务代码

try{


}finaly{

l.unlock;

       }

   }

3.等待是否可中断

Synchronized是不可中断的。除非抛出异常或者正常运行完成

Lock可以中断的。中断方式:

调用设置超时方法tryLock(long timeout ,timeUnit unit)

调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断

4.加锁的时候是否公平

Synchronized是非公平锁

Lock既可以是公平锁也可以是非公平锁,它默认是非公平锁,在调用其构造方法时可以传入Boolean类型的参数,如果是true,则是公平锁;如果false,则是非公平锁。

5.能否精准唤醒

Synchronized: 不能精准唤醒,要么随机唤醒一个线程;要么是唤醒所有等待的线程。

Lock: 用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像Synchronized那样,不能精确唤醒线程。

6.性能比较

Synchronized是托管给JVM执行的,而Lock是Java写的控制锁的代码。

在Java1.5中,Synchronized是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。

但是到了Java1.6版本,发生了变化。Synchronized在语义上很清晰,可以进行很多优化,实现了许多以前没有的操作,Synchronized的性能这个时候并不比Lock性能差,并且官方也表示,他们更支持Synchronized,在未来的版本中还有优化的空间。.

7. 从使用锁的方式比较

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转換线程阻塞时会引起线程上下文切换,当有很多线程竟争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有沖突而去完成某项操作,如果因为沖突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作( Compare and Swap)。我们可以进一步研究 Reentrantlock的源代码,会发现其中比较重要的获得锁的一个方法是compareandsetstate。这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干抗,而 compareandSet)就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

用法

Synchronized

Synchronized是Java的关键字,当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只有一个线程执行该代码。因为当调用Synchronized修饰的代码时,并不需要显示的加锁和解锁的过程,所以称之为隐式锁。

Sychronized的用法:

1、同步方法体,在方法声明中使用,如下:

public synchronized void method(){

       //方法体

}

2、同步代码块,修饰在代码块外层,指定加锁对象,如下:

public void method2(){

   synchronized (this) {

   //一次只能有一个线程进入  

   }

}

上述synchronized(this)指定了当前对象本身作为锁,和它持有相同对象锁的地方将产生互斥性。当一个线程访问method2的同步代码块时,它就获得了这个object的对象锁。其他的线程对该object所有同步代码部分的访问都被暂时的阻塞。

sychronized的不同写法对程序响应的快慢和对资源高并发的利用程度不一样,性能和执行效率从差到优排序如下:

同步方法体 < 同步代码块 < 小对象锁同步代码块

小对象锁同步代码块指锁的对象的所占内存小,因为锁是对象,加锁和解锁都需要释放资源,那肯定是锁对象越小越好,实际应用如下:

小对象锁同步代码块指锁的对象的所占内存小,因为锁是对象,加锁和解锁都需要释放资源,那肯定是锁对象越小越好,实际应用如下:

private byte[] lock = new byte[1];

public void method3(){

   synchronized (lock) {

       //一次只能有一个线程进入  

   }

Lock

Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁。下面针对Lock的几个实现类ReentrantLock、ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock解析。

ReentrantLock(可重入锁),是一个互斥的同步器,用ReentrantLock实现同步机制比sychronized实现更具伸缩性。使用如下:

private final ReentrantLock lock = new ReentrantLock();

   public void m(){

       lock.lock();//获得锁

       try{

           //方法体

       }finally{

           lock.unlock();//务必释放锁

       }

   }

注意:在使用ReentrantLock时,一定要有释放锁的操作。

ReadWriteLock(读写锁)是一个接口,提供了readLock和writeLock两种锁的操作,也就是说一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁应用的场景是一个资源被大量读取操作,而只有少量的写操作。其源码:

public interface ReadWriteLock {

   Lock readLock();

   Lock writeLock();

}

ReadWriteLock借助Lock来实现读写两个锁并存、互斥的机制。每次读取共享数据就需要读取锁,需要修改共享数据就需要写入锁。

ReadWriteLock借助Lock来实现读写两个锁并存、互斥的机制。每次读取共享数据就需要读取锁,需要修改共享数据就需要写入锁。

读写锁的机制:1、读-读不互斥,读线程可以并发执行;2、读-写互斥,有写线程时,读线程会堵塞;3、写-写互斥,写线程都是互斥的。

使用方法:

/创建ReentrantReadWriteLock对象

   private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();


//抽取读写锁

private Lock readLock = rwl.readLock();

private Lock writeLock = rwl.writeLock();

public int getXXX(){

   readLock.lock();

   try{

       //执行操作

   }finally{

       readLock.unlock();

   }

}

public void setXXX(){

   writeLock.lock();

   try{

       //执行操作

   }finally{

       writeLock.unlock();

   }

}

ReentrantReadWriteLock和ReentrantLock的比较:ReentrantReadWriteLock是对ReentrantLock的复杂扩展,能适合更加复杂的业务场景,ReentrantReadWriteLock可以实现一个方法中读写分离的锁的机制。而ReentrantLock只是加锁解锁一种机制。

最后对比Synchronized、ReentrantLock和ReentrantReadWriteLock:

Synchronized是在JVM层面上实现的,无需显示的加解锁,而ReentrantLock和ReentrantReadWriteLock需显示的加解锁,一定要保证锁资源被释放;Synchronized是针对一个对象的,而ReentrantLock和ReentrantReadWriteLock是代码块层面的锁定;ReentrantReadWriteLock引入了读写和并发机制,可以实现更复杂的锁机制,并发性相对于ReentrantLock和Synchronized更高。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。