天天看点

Java并发编程札记-(四)JUC锁-02Lock与ReentrantLock

今天学习Lock与ReentrantLock。

Java中的锁有两种,synchronized与Lock。因为使用synchronized并不需要显示地加锁与解锁,所以往往称synchronized为隐式锁,而使用Lock时则相反,所以一般称Lock为显示锁。想了解synchronized更多请移步Java并发编程札记-(一)基础-06synchronized详解。

synchronized修饰方法或语句块,所有锁的获取和释放都必须出现在一个块结构中。当需要灵活地获取或释放锁时,synchronized显然是不符合要求的。Lock接口的实现允许锁在不同的范围内获取和释放,并支持以任何顺序获取和释放多个锁。

一句话,Lock实现比synchronized更灵活。但凡事有利就有弊,不使用块结构锁就失去了使用synchronized修饰方法或语句时会出现的锁自动释放功能,在大多数情况下,Lock实现需要手动释放锁。

除了更灵活之外,Lock还有以下优点:

  • Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
  • Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。

ReentrantLock是一个可重入的互斥锁。顾名思义,“互斥锁”表示在某一时间点只能被同一线程所拥有。“可重入”表示锁可被某一线程多次获取。

当锁没有被某一线程占有时,调用lock()方法的线程将成功获取锁。可以使用isHeldByCurrentThread()和 getHoldCount()方法来判断当前线程是否拥有该锁。

ReentrantLock既可以是公平锁又可以是非公平锁。当此类的构造方法ReentrantLock(boolean fair) 接收true作为参数时,ReentrantLock就是公平锁,线程依次排队获取公平锁,即锁将被等待最长时间的线程占有。与默认情况(使用非公平锁)相比,使用公平锁的程序在多线程环境下效率比较低。而且公平锁不能保证线程调度的公平性,tryLock方法可在锁未被其他线程占用的情况下获得该锁。

方法列表

//构造方法摘要
ReentrantLock() 
          //创建一个 ReentrantLock 的实例。
ReentrantLock(boolean fair) 
          //创建一个具有给定公平策略的 ReentrantLock。  
//方法摘要
int getHoldCount() 
          //查询当前线程保持此锁的次数。
protected  Thread   getOwner() 
          //返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected  Collection<Thread>   getQueuedThreads() 
          //返回一个 collection,它包含可能正等待获取此锁的线程。
 int    getQueueLength() 
          //返回正等待获取此锁的线程估计数。
protected  Collection<Thread>   getWaitingThreads(Condition condition) 
          //返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
 int    getWaitQueueLength(Condition condition) 
          //返回等待与此锁相关的给定条件的线程估计数。
 boolean    hasQueuedThread(Thread thread) 
          //查询给定线程是否正在等待获取此锁。
 boolean    hasQueuedThreads() 
          //查询是否有些线程正在等待获取此锁。
 boolean    hasWaiters(Condition condition) 
          //查询是否有些线程正在等待与此锁有关的给定条件。
 boolean    isFair() 
          //如果此锁的公平设置为 true,则返回 true。
 boolean    isHeldByCurrentThread() 
          //查询当前线程是否保持此锁。
 boolean    isLocked() 
          //查询此锁是否由任意线程保持。
 void   lock() 
          //获取锁。
 void   lockInterruptibly() 
          //如果当前线程未被中断,则获取锁。
 Condition  newCondition() 
          //返回用来与此 Lock 实例一起使用的 Condition 实例。
 String toString() 
          //返回标识此锁及其锁定状态的字符串。
 boolean    tryLock() 
          //仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
 boolean    tryLock(long timeout, TimeUnit unit) 
          //如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
 void   unlock() 
          //试图释放此锁。
           

使用最典型的代码如下

class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...

    public void m() { 
        lock.lock();  // block until condition holds
        try {
            // ... method body
        } finally {
            lock.unlock()
        }
    }
}
           

修改下Java并发编程札记-(一)基础-06synchronized详解中火车票订票系统实例,使用ReentrantLock实现线程安全。

例6:火车票订票系统-ReentrantLock实现线程安全版

import java.util.concurrent.locks.ReentrantLock;

public class SellTickets {

    public static void main(String[] args) {
        TicketsWindow tw1 = new TicketsWindow();
        Thread t1 = new Thread(tw1, "一号窗口");
        Thread t2 = new Thread(tw1, "二号窗口");
        t1.start();
        t2.start();
    }
}

class TicketsWindow implements Runnable {
    private int tickets = ;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (tickets > ) {
                    System.out.println(Thread.currentThread().getName() + "还剩余票:" + tickets + "张");
                    --tickets;
                    System.out.println(Thread.currentThread().getName() + "卖出一张火车票,还剩" + tickets + "张");
                } else {
                    System.out.println(Thread.currentThread().getName() + "余票不足,暂停出售!");
                    try {
                        Thread.sleep( * );
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
           

在某些方法的参数中出现了Condition,在后面我们会详细介绍Condition。

总结

  • 与synchronized 相比ReentrantLock的使用更灵活。Lock接口的实现允许锁在不同的范围内获取和释放,并支持以任何顺序获取和释放多个锁。
  • ReentrantLock具有与使用 synchronized 相同的一些基本行为和语义,但功能更强大。包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
  • ReentrantLock具有synchronized所没有的许多特性,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。
  • ReentrantLock可伸缩性强,应当在高度争用的情况下使用它。

本文就讲到这里,想了解Java并发编程更多内容请参考:

  • Java并发编程札记-目录

参考:

  • JDK 5.0 中更灵活、更具可伸缩性的锁定机制

END.