并发编程[7]_ReentrantLock

ReentrantLock 翻译为可重入锁,是 java.util.concurrent.locks 包中的一个类,可以通过代码调用lock() 和 unlock() 方法来进行加锁解锁。因此一般使用的时候要用到try,finally,在finally中进行锁的释放。

1. ReentrantLock 和 synchronized

ReentrantLock 和 synchronized 这两种同步方式,有相同也有不同之处,可见下表:

synchronized ReentrantLock
不需手动释放和开启锁,关键字可修饰方法,代码块 手动获取和释放锁
重量级锁 轻量级锁
可重入 可重入
不可设置超时 可设置超时
不可中断 可中断和不可中断都有
非公平锁 公平锁和非公平锁都有,默认非公平锁
单一等待队列 多个等待队列,绑定多个Condition类

2. ReentrantLock 特性及例子

  • 可打断

创建ReentrantLock的实例lock,在主线程对lock加锁,在线程t1等待锁的时候,主线程打断。
使用 lockInterruptibly() 表示是可处理打断的锁。

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                log.debug("准备获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.debug("被打断,没有获取到锁");
                return;
            }

            try {
                log.debug("获取到了锁");
            } finally {
                lock.unlock();
                log.debug("释放锁");
            }
        }, "t1");

        lock.lock();
        try {
            t1.start();
            Thread.sleep(1000);
            t1.interrupt();     // 打断
        }finally {
            lock.unlock();
        }
    }
}

运行结果:

2021-04-28 22:39:26.745  [t1] - 准备获取锁
2021-04-28 22:39:27.745  [t1] - 被打断,没有获取到锁
  • 可超时

创建ReentrantLock的实例lock,在主线程对lock加锁,在线程t1等待锁的时候,超时返回false退出。
tryLock() 有返回值,返回true,表示获取到了锁。返回false,表示没有获取到锁,拿不到锁不会一直等着,会返回false。
tryLock(long timeout, TimeUnit unit) 方法,可进行超时时间的设置。

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                log.debug("准备获取锁");
                if(!lock.tryLock(3, TimeUnit.SECONDS)){
                   log.debug("超时了,没有获得锁。");
                   return;
                }
            } catch (InterruptedException e) {
                log.debug("被打断,没有获取到锁");
                return;
            }

            try {
                log.debug("获取到了锁");
            } finally {
                lock.unlock();
                log.debug("释放锁");
            }
        }, "t1");

        lock.lock();
        try {
            t1.start();
            Thread.sleep(5000);
        }finally {
            lock.unlock();
        }
    }
}

运行结果:

2021-04-28 22:47:26.838  [t1] - 准备获取锁
2021-04-28 22:47:29.842  [t1] - 超时了,没有获得锁。
  • 多个等待队列

在使用synchronized时,唤醒线程不能根据条件来进行唤醒,只能唤醒全部线程notifyAll或者随机唤醒notify,所以,当需要不同条件来唤醒时,ReentrantLock提供了newCondition方法,来创建条件对象。
和wait(),notify()类似的方法有Condition中的await() 和 signal(),当然使用前也要获取对应的锁。
下面的例子,是先利用lock实例创建两个condition实例,然后在每个线程中,调用不同condition的await()方法。唤醒的时候,也是调用Condition中的signal()或者signalAll()方法,来唤醒线程。

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // lock的两个等待队列,可以分别往不同的条件对象加入线程。
        // 每个condition绑定不同线程,唤醒的时候就可以分开唤醒了。
        Condition animalCondition = lock.newCondition();
        Condition plantCondition = lock.newCondition();

        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                try {
                    log.debug("cat waiting...");
                    // 和wait()方法类似,也可加时间参数
                    animalCondition.await();
                    log.debug("cat awakened");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        }, "cat");
        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                try {
                    log.debug("rose waiting");
                    plantCondition.await();
                    log.debug("rose awakened");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        }, "rose");

        t1.start();
        t2.start();

        // 唤醒cat
        Thread.sleep(3000);
        lock.lock();
        try {
            animalCondition.signal();
        }finally {
            lock.unlock();
        }

        // 唤醒rose
        Thread.sleep(3000);
        lock.lock();
        try {
            plantCondition.signal();
        }finally {
            lock.unlock();
        }
    }
}

运行结果:

2021-04-28 23:27:59.269  [cat] - cat waiting...
2021-04-28 23:27:59.269  [rose] - rose waiting
2021-04-28 23:28:02.269  [cat] - cat awakened
2021-04-28 23:28:05.269  [rose] - rose awakened

从打印的时间可以看出唤醒的先后顺序不同。

posted @ 2024-08-23 10:52  Aeons  阅读(4)  评论(0编辑  收藏  举报