Java并发编程:Lock

Lock概述

  • 位于java.util.concurrent.locks包内
  • 主要目的是和synchronized一样,两者都是为了解决同步问题,处理资源争端而产生的技术 

java.util.concurrent.locks包下常用的类

Lock

lock为一个接口,主要方法如下

lock(),tryLock(),tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁。

unLock()方法用来释放锁。

newCondition()用来实现类似wait(),notify()。

lock()方法算是使用频率最高的一个方法,就是用来获取锁。如果锁被其他线程获取,则等待。因为发生异常锁也不会被释放,所以lock必须在try{}catch{}块中进行,并且在finally块中释放锁,防止死锁发生,一般使用方式如下:

Lock lock = new ReentrantLock();
lock.lock();
try{
    // 处理任务
}catch(Exception e){
     
}finally{
    lock.unlock();   //释放锁
}

tryLock()方法,尝试获取锁,如果获取成功,返回true,获取失败(被其他线程占用)则返回false,立即返回,不会等待

tryLock(long time, TimeUnit unit)和tryLock方法类似,区别在于获取锁的时候指定等待一定的时间,如果过了这个时间还没有获取锁,则返回false,一般情况下使用方式如下:

Lock lock = new ReentrantLock();
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

lockInterruptibly()方法获取锁的时候,如果线程正在等待获取锁,则中断这个线程的等待状态。打个比方,两个线程同时通过lock.lockInterruptibly()方法获取锁时,如果线程A获取了锁,线程B只能等待,那么对线程B调用b.interrupt()方法能够终端线程B的等待过程,使用方式如下

public void method() throws InterruptedException {
  //如果需要正确中断等待锁的过程,必须将锁放在try外面,然后将异常抛出
  lock.lockInterruptibly(); 
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
} 

注意:当一个线程获取锁之后,是不会被interrupt()方法中断,只能中断阻塞过程中的线程,所以当通过lockInterruptibly()方法获取某个锁的时候,如果不能获取到,只有在等待的情况下,才可以响应中断

而使用synchronized修饰的话,当一个线程处理等待获取锁的状态,是无法被中断的,只有一只等待下去

ReentrantLock

为可重入锁。可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。意味着线程可以进入它已经拥有的锁的同步代码块儿。

lock一般声明为成员变量,局部变量的话,属于每次方法调用产生的实例,调用lock()方法自然获取的是不同的锁

  1. 先new一个实例
    static ReentrantLock r=new ReentrantLock();
  2. 加锁
    r.lock()或r.lockInterruptibly();
    此处不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,
    那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception或catch)
  3. 释放锁
    r.unlock();
    必须做!要放在finally里面。以防止异常跳出了正常流程,导致灾难。(哪怕发生了OutofMemoryError,finally块中的语句执行也能够得到保证)

其他一些方法

isFair() // 是否公平锁

isLocked() // 是否被任何线程获取了

isHeldByCurrentThread() // 是否被当前线程获取了

hasQueuedThreads() // 是否有线程在等待该锁

ReadWriteLock

也是一个接口,顾名思义为读写锁,声明如下

将读写分离,变成了两把锁,从而形成读写不互斥

ReentrantReadWriteLock

是ReadWriteLock的具体实现

  • 可重入读写锁(读写锁的一个实现)
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock()
    ReadLock r = lock.readLock();
    WriteLock w
    = lock.writeLock();
  • 两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码

Lock和synchronized的区别

其实本质上两则是一样的,只不过lock更加强大

  • Lock是jdk中一组类库,synchronized是Java语言的关键字,属于语言的特性
  • 提供读写锁,公平锁
  • lock可以知道有没有获取锁,synchronized不行
  • synchronized发生异常时,会自动释放线程占有的锁,而Lock必须主动通过unLock()来释放,不然可能造成死锁,因此使用unLock()方法需要放在finally块中
  • lock可以让等待锁的线程中断,synchronized不行,需要一直等待

至于两者之间的选择,对并发量大,资源竞争激烈的场景,使用Lock下面的类库性能还是不错的,并发量小,两者差不多,synchronized足矣

 

《Java并发编程实践》一书给出了使用 ReentrantLock的最佳时机:

当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用synchronized。     

还有一点,我觉得synchronized可以指定获取哪一把对象锁,这个还是挺实用的

posted @ 2018-03-25 16:47  hy_wx  阅读(130)  评论(0编辑  收藏  举报