3.11、ReentrantLock

相对于synchronized它具备以下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与synchronized一样,都支持可重入

基本语法

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();
        }
    }

输出

2022/03/06-22:23:07.927 [main] c.Test1 - execute method1
2022/03/06-22:23:07.927 [main] c.Test1 - execute method2
2022/03/06-22:23:07.927 [main] c.Test1 - execute method3

可打断

public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("start..");
            try {
                lock.lockInterruptibly();
                log.debug("获得了锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("interrupt..");
                return;
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("lock get");
        t1.start();
        try {
            Thread.sleep(1000);
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

输出

2022/03/06-22:28:23.876 [main] c.Test2 - lock get
2022/03/06-22:28:23.876 [t1] c.Test2 - start..
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 reentrantlocktest.Test2.lambda$main$0(Test2.java:20)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "t1" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at reentrantlocktest.Test2.lambda$main$0(Test2.java:27)
	at java.lang.Thread.run(Thread.java:748)
2022/03/06-22:28:24.889 [t1] c.Test2 - interrupt..

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

public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("start..");
            try {
                lock.lock();
                log.debug("lock get");
                return;
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("lock get");
        t1.start();
        try {
            Thread.sleep(1000);
            t1.interrupt();
            log.debug("interrupt");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            log.debug("unlock");
        }
    }

输出

2022/03/06-22:31:12.715 [main] c.Test2 - lock get
2022/03/06-22:31:12.715 [t1] c.Test2 - start..
2022/03/06-22:31:13.715 [main] c.Test2 - interrupt
2022/03/06-22:31:13.715 [main] c.Test2 - unlock
2022/03/06-22:31:13.715 [t1] c.Test2 - lock get

锁超时

立刻失败

public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("start..");
            if (!lock.tryLock()) {
                log.debug("lock get fail!!");
                return;
            }
            try {
                log.debug("lock get");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("lock get");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

输出

2022/03/06-22:33:54.606 [main] c.Test4 - lock get
2022/03/06-22:33:54.608 [t1] c.Test4 - start..
2022/03/06-22:33:54.608 [t1] c.Test4 - lock get fail!!

超时失败

public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("start..");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("lock get fail!!");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("lock get");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("lock get");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

输出

2022/03/06-22:35:15.945 [main] c.Test4 - lock get
2022/03/06-22:35:15.945 [t1] c.Test4 - start..
2022/03/06-22:35:16.958 [t1] c.Test4 - lock get fail!!

使用tryLock解决哲学家就餐问题

public class Chopstick extends ReentrantLock {

    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class Philosoper extends Thread {

    Chopstick left;
    Chopstick right;

    public Philosoper(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    public void eat() {
        try {
            log.debug("eating");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            if (left.tryLock()) {
                try {
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
}

公平锁

ReentrantLock默认是不公平的

public static void main(String[] args) throws InterruptedException {
        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");
//                    log.debug("running");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        Thread.sleep(200);
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "running");
//                log.debug("running");
            } finally {
                lock.unlock();
            }
        }, "强制进入").start();
        lock.unlock();
    }

注意
该实验不一定总能复现

输出

t0running
t31running
t4running
强制进入running
t5running
t8running

改为公平锁后

ReentrantLock lock = new ReentrantLock(true);

总是在最后输出

t497running
t498running
t499running
强制进入running

条件变量

synchronized中也有条件变量,就是我们讲原理时那个WaitSet休息室,当条件不满足时进入WaitSet等待

ReentrantLock的条件变量比synchronized强大之处在于,它是支持多个条件变量的,这就好比

  • synchronized是那些不满足条件的线程都在一间休息室等消息
  • 而ReentrantLock支持多间休息室,有专门等烟的休息室,专门等早餐的休息室,唤醒时也是按休息室来唤醒

使用要点:

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

例:

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

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigarette) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("waited cigarette");
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitBreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("waited breakfast");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
        Thread.sleep(1000);
        sendCigarette();
        Thread.sleep(1000);
        sendBreakFast();
    }

    public static void sendCigarette() {
        lock.lock();
        try {
            log.debug("cigarette arrive");
            hasCigarette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void sendBreakFast() {
        lock.lock();
        try {
            log.debug("breakfast arrive");
            hasBreakfast = true;
            waitBreakfastQueue.signal();
        } finally {
            lock.unlock();
        }
    }

输出

2022/03/06-23:09:03.015 [main] c.Test6 - cigarette arrive
2022/03/06-23:09:03.015 [t1] c.Test6 - waited cigarette
2022/03/06-23:09:04.017 [main] c.Test6 - breakfast arrive
2022/03/06-23:09:04.017 [t2] c.Test6 - waited breakfast
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值