ReentrantLock

93 阅读4分钟

ReentrantLock简介

ReentrantLock相对于synchronized它具备如下特点:

  • 可中断。
  • 可以设置超时时间。
  • 可以设置为公平锁。(可以解决饥饿的问题)
  • 支持多个条件变量。

synchronized一样,都支持可重入。

基本语法

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

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

@Slf4j
public class Test {
    
    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();
        }
    }
}

//输出
17:59:11.862 [main] c.TestReentrant - execute method1
17:59:11.865 [main] c.TestReentrant - execute method2
17:59:11.865 [main] c.TestReentrant - execute method3

可打断

@Slf4j
public class Test {

    public static void main(String[] args) {
        
        ReentrantLock lock = new ReentrantLock();
        
        Thread t1 = new Thread(() -> {
        
            log.debug("启动...");
            try {
                // 如果没有竞争,该方法就会获取Lock对象锁。
                // 如果有竞争,就会进入阻塞队列,可以被其它线程用interruput方法打断。
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("等锁的过程中被打断");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        
        try {
            Thread.sleep(1);
            t1.interrupt();
            log.debug("执行打断");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

//输出:
18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException at
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)at
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断

注意如果是不可中断模式,那么即使使用了interrupt也不会让等待中断。

@Slf4j
public class Test {
public static void main(String\[] args) {

       ReentrantLock lock = new ReentrantLock();

            Thread t1 = new Thread(() -> {
                log.debug("启动...");
                lock.lock();
                try {
                    log.debug("获得了锁");
                } finally {
                    lock.unlock();
                }
            }, "t1");
            lock.lock();
            log.debug("获得了锁");
            t1.start();

            try {
                TimeUnit.SECONDS.sleep(2);
                t1.interrupt();
                log.debug("执行打断");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                log.debug("释放了锁");
                lock.unlock();
            }

            //输出
            18:06:56.261 [main] c.TestInterrupt - 获得了锁
            18:06:56.265 [t1] c.TestInterrupt - 启动...
            18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
            18:06:58.267 [main] c.TestInterrupt - 释放了锁
            18:06:58.267 [t1] c.TestInterrupt - 获得了锁
        }
    }

锁超时

@Slf4j
public class Test {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            if (!lock.tryLock()) {
                log.debug("获取立刻失败,返回");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");

        t1.start();
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    //输出:
    18:15:02.918 [main] c.TestTimeout - 获得了锁
    18:15:02.921 [t1] c.TestTimeout - 启动...
    18:15:02.921 [t1] c.TestTimeout - 获取立刻失败,返回
}

超时失败

@Slf4j
public class Test {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("获取等待 1s 后失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    //输出:
    18:19:40.537 [main] c.TestTimeout - 获得了锁
    18:19:40.544 [t1] c.TestTimeout - 启动...
    18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回
}

公平锁

ReentrantLock默认是不公平的。


    ReentrantLock lock = new ReentrantLock(false);
    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 之后去争抢锁
    Thread.sleep(1000);
    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();

强行插入,有机会在中间输出.注意:该实验不一定总能复现

t39 running...

t40 running...

t41 running...

t42 running...

t43 running...

强行插入 start...

强行插入 running...

t44 running...

t45 running...

t46 running...

t47 running...

t49 running...

改为公平锁后,ReentrantLock lock = new ReentrantLock(true); 强行插入,总是在最后输出

t465 running...

t464 running...

t477 running...

t442 running...

t468 running...

t493 running...

t482 running...

t485 running...

t481 running...

强行插入 running...

公平锁一般没有必要,会降低并发度。

条件变量

synchronized中也有条件变量,就是那个waitSet,当条件不满足时进入waitSet等待 ReentrantLock的条件变量比synchronized强大之处在于,它是支持多个条件变量的,这就好比 synchronized是那些不满足条件的线程都在同一个waitSet等待,而ReentrantLock支持多个waitSet,可以根据条件的不同,进入不同的waitSet

使用要点:

  • await前需要获得锁。
  • await执行后,会释放锁,进入conditionObject等待。
  • await的线程被唤醒(或打断、或超时)取重新竞争lock锁。
  • 竞争lock锁成功后,从await后继续执行。

static ReentrantLock lock = new ReentrantLock();

//创建一个waitCigaretteQueue的条件变量对象
static Condition waitCigaretteQueue = lock.newCondition();

//创建一个waitbreakfastQueue的条件变量对象
static Condition waitbreakfastQueue = lock.newCondition();

static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;

public static void main (String[]args){

    new Thread(() -> {
        try {
            lock.lock();
            while (!hasCigrette) {
                try {
                    //线程在waitCigaretteQueue中进行等待
                    waitCigaretteQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("等到了它的烟");
        } finally {
            lock.unlock();
        }
    }).start();
    
    new Thread(() -> {
        try {
            lock.lock();
            while (!hasBreakfast) {
                try {
                    //线程在waitbreakfastQueue中进行等待
                    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("送烟来了");
        hasCigrette = true;
        //叫醒该waitCigaretteQueue中的一个线程
        waitCigaretteQueue.signal();
    } finally {
        lock.unlock();
    }
}

private static void sendBreakfast () {
    lock.lock();
    try {
        log.debug("送早餐来了");
        hasBreakfast = true;
        //叫醒该waitbreakfastQueue中的一个线程
        waitbreakfastQueue.signal();
    } finally {
        lock.unlock();
    }
}