特点

主要是与synchronized进行比较,它具有如下特点 :

  • 可中断。只要加锁后,别的线程就不能中断synchronized的加锁,只能等它执行完成。
  • 可以设置超时时间。规定时间内,如果ReentrantLock没有获得锁,那就放弃,不锁了x
  • 可以设置公平/非公平
  • 支持多个条件变量。精准唤醒。

基本语法:

reentrantLock.lock();
try{
    // 临界区
}finally{
    // 释放锁
    reentrantLock.unlock();
}

可重入

可重入是指一个线程如果首次获得这把锁,因为它是这把锁的拥有者,有权利再次获得这把锁。

如果是不可重入的,当第二次获得时,自己会被锁阻止 。

static ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) {
    // 测试可重入
    method1();
}

public static void method1() {
    lock.lock();
    try {
        log.debug("execute method1");
        method2();
    } finally {
        lock.unlock();
    }
}

public static void method2() {
    lock.lock();
    try {
        log.debug("execute method2");
        method3();
    } finally {
        lock.unlock();
    }
}

public static void method3() {
    lock.lock();
    try {
        log.debug("execute method3");
    } finally {
        lock.unlock();
    }
}

可打断

lock.lockInterruptibly()表示这个加锁是可以被打断的,如果使用lock.lock()是不可以被打断的。

如果没有竞争,这个方法可以获得lock对象锁。

如果有竞争,就进入阻塞队列,可以被其它线程用interrupt方法打断 。

下面展示的情况就是有竞争的时候,t1线程被阻塞,然后被main打断,抛出InterruptedException。

(注,如果是用lock(),即使interrupt也不会让等待中断。)

public static void canInterrupt() {
    Thread t1 = new Thread(() -> {
        log.debug("启动...");
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.debug("等锁的过程中被打断");
            return;
        }
        try {
            log.debug("获得了锁");
        } finally {
            lock.unlock();
        }
    }, "t1");
    // 这里已经被main线程加锁了
    lock.lock();
    log.debug("获得了锁");
    t1.start();
    try {
        sleep(1);
        t1.interrupt();
        log.debug("执行打断");
    } finally {
        lock.unlock();
    }
}

锁超时

lock.tryLock(),尝试获取锁,获取不到,马上返回。

lock.tryLock(1, TimeUnit.SECONDS),尝试获取锁,1s后还获取不到,马上返回。

public static void timeLimit(){
    Thread t1 = new Thread(() -> {
        log.debug("启动...");
        try {
            if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                //            if (!lock.tryLock()) {
                log.debug("获取立刻失败,返回");
                return;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            log.debug("获得了锁");
        } finally {
            lock.unlock();
        }
    }, "t1");
    lock.lock();
    log.debug("获得了锁");
    t1.start();
    try {
        sleep(2);
        //            sleep(0.5);
    } finally {
        lock.unlock();
    }
}

(这个tryLock()也可以去解决哲学家就餐问题,尝试获取左手筷子后,再尝试获取右手筷子,这样就能保证都能拿到锁。)

公平锁

ReentrantLock默认是不公平的。

一般不设置公平锁,因为会降低并发度。

下面的例子,如果是公平锁,只能在最后才

public static void isFair() {
    lock.lock();
    for (int i = 0; i < 500; i++) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
                lock.unlock();
            }
        }, "t" + i).start();
    }
    // 1s 之后去争抢锁,抢得到,说明它是非公平的
    sleep(1);
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " start...");
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " running...");
        } finally {
            lock.unlock();
        }
    }, "强行插入").start();
    lock.unlock(); 
}

条件变量

synchronized中也有条件变量,当不满足条件时,会进入WaitSet等待。

但是它仅有一个,而ReentrantLock条件变量更强大,支持多个!

基本使用
// 条件变量,可以定义多个
Condition cond1 = lock.newCondition();
Condition cond2 = lock.newCondition();
// 要先获得锁
lock.lock();
// 进入“休息室”等待
cond1.await();
// 叫醒cond1“休息中”的其中一个线程
cond1.signal();
// 叫醒cond1“休息中”的所有线程
cond1.signalAll();
  1. 使用前要获得锁
  2. 执行await后会释放锁,进入confitionObject等待
  3. await的线程被唤醒、打断、超时后,需要重新竞争锁
  4. 竞争成功后,才继续执行await后面的内容。
实践

感觉。。与Object的wati和notify差不多啊

static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitBreakfastQueue = lock.newCondition();
static volatile boolean hasCigarette = false;
static volatile boolean hasBreakfast = false;

private static void bestTry() {
    new Thread(() -> {
        try {
            lock.lock();
            // 与Object的wait相同,会不会也有虚假唤醒呢?
            while (!hasCigarette) {
                try {
                    waitCigaretteQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("等到了它的烟");
        } finally {
            lock.unlock();
        }
    }).start();
    new Thread(() -> {
        try {
            lock.lock();
            while (!hasBreakfast) {
                try {
                    waitBreakfastQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("等到了它的早餐");
        } finally {
            lock.unlock();
        }
    }).start();
    sleep(1);
    sendBreakfast();
    sleep(1);
    sendCigarette();
}

private static void sendCigarette() {
    lock.lock();
    try {
        log.debug("送烟来了");
        hasCigarette = true;
        waitCigaretteQueue.signal();
    } finally {
        lock.unlock();
    }
}

private static void sendBreakfast() {
    lock.lock();
    try {
        log.debug("送早餐来了");
        hasBreakfast = true;
        waitBreakfastQueue.signal();
    } finally {
        lock.unlock();
    }
}