synchronized的功能的扩展:重入锁

重入锁

重入锁可以说是synchronized,Object.wait(),Object.notify()的一种替代品。

在JDK5的早期版本,重入锁的新能要比synchronized好很多,在JDK6后对synchronized进行可很多优化,使得他和重入锁的性能差距并不大。

重入锁使用java.util.concurrent.locks.ReentrantLock类实现,下面我么来看下重入锁的简单使用案例:

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLock implements Runnable {
    public static ReentrantLock lock=new ReentrantLock();
    public static int i=0;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int j=0;j<10000000;j++) {
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        ReenterLock tl=new ReenterLock();
        Thread t1=new Thread(tl);
        Thread t2=new Thread(tl);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }

}

运行程序可以得到结果为20000000.

重入锁具有很高的灵活性,需要开发人员手动用lock()与unlock()函数来指定何时加锁何时解锁,但是需要注意的是,在离开临界区的时候要记得释放锁,否则其他线程就没有机会在访问临界区了。(之所以叫重入锁是因为这种锁可以反复进入,但是记得一个线程同时获得多少锁,也必须释放相同的次数)

重入锁除了使用上的灵活性,还有一些高级的功能。

中断响应

对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么获得锁继续执行,要么保持等待。但是使用重入锁。则提供了另外一种可能,就是线程可以被中断。也就是说,在等待锁的过程中,线程可以取消对锁的请求。有些时候这么做是很有必要的,比如和好朋友约好去打球,等了半小时没到,街道电话得知朋友临时有事,不能来了,那么就打道回府了。这种情况对于处理死锁有一定的帮助。使用lockInterruptibly()函数表示重入锁可以响应中断。

锁申请限时等待

除了等待外部通知外,要避免死锁还有一种办法,就是限时等待。我么可以通过tryLock方法进行一次限时的等待。

try{
    if(lock.tryLock(5,TimeUnit.SECONDS)){
        Thread.sleep(6000);
        }else{
            System.out.println("get lock failed");
        }
    }catch(InterruptdeException e){
        e.printStackTrace();
    }finally{
    if(lock.isHeldByCurrentThread()) lock.unlock();}
}

上面代码中,设置的5秒的限时等待,由于睡眠了6秒,或导致请求锁失败。
tryLock也可以不带参数进行运行,这种情况下,线程会尝试获得锁,如果锁未被其他线程占用,申请锁成功,返回true,否则申请失败,线程也不会进行等待,直接返回false。

公平锁

大多数情况下锁是不公平的,也就是说不一定先请求就先获得锁,使用synchronized关键字实现锁,锁就是不公平的,重入锁可以实现公平锁,避免现象,也就是说,只要你排队,就能获得锁,但是由于需要维护一个有序队列,导致公平锁的实现成本比较高,性能相对也非常低下。重入锁有如下一个构造函数:
public ReentrantLock(boolean fair)
当参数为true时锁是公平的的。

重入锁的好搭档:Condition条件

Condition与Object.wait(),Object.notify()方法的作用大致相同,只不过是用来与重入锁相关联的。通过Lock接口(出入锁就实现乐这一接口)的Condition newCondition()方法可以生成与当前重入锁对象绑定的Condition实例,利用Condition对象,我们可以让线程在特定的时刻进行等待,或者在某一个特定的时刻得到通知,继续执行。
Condition接口提供的方法如下。

void await() throws InterrupttedExceptionI;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterrupttedExceptionI;
boolean await(long time,TimeUnit unit) throws InterrupttedExceptionI;
boolean  awaitUntil(Date deadline)  throws InterrupttedExceptionI;
void signal();
void siganlAll();

以上个方法的含义如下:

  • await()方法会使当前线程等待,同时会释放锁,当其他线程中使用signal()或signalAll()方法的时候,线程会重新获得锁继续执行。或者线程被中断是也能跳出等待。
  • awaitUninterruptibly()方法与await()方法基本相同,但是他不会在等待过程中相应或者相应中断。
  • signal()用于唤醒一个在等待中的线程,同理signalAll()用于唤醒所有在等待中的线程。
上一篇:国产微服务 网关Apache APISIX 上手


下一篇:Facebook允许用户查看并删除搜索历史