【并发编程】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();
     }
   }

输出

 可打断

示例

不能用lock()了,用lockInterruptibly()

如果没有竞争,此方法就会获取lock对象

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

ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {

        log.debug("启动...");
        try {
            //不能用lock()了,用lockInterruptibly()
            //
        lock.lockInterruptibly();
        } catch (InterruptedException e) {
        e.printStackTrace();
        log.debug("等锁的过程中被打断");
        return;
        }
          try {
        log.debug("获得了锁");
       } finally {
         lock.unlock();
       }
     }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
        sleep(1);
        t1.interrupt();
        log.debug("执行打断");
        } finally {
        lock.unlock();
        }

运行结果 

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

锁超时

lock.tryLock() 尝试获取锁,返回boolean

立刻失效
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 {
        sleep(2);
    } finally {
        lock.unlock();
    }

}

输出

超时失效
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 {
    sleep(2);
} finally {
    lock.unlock();
}

输出

使用 tryLock 解决哲学家就餐问题
class Chopstick extends ReentrantLock {
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    @Override
    
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if (left.tryLock()) {
                try {
                    // 尝试获得右手筷子
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(1);
    }
}

公平锁(FIFO)

本意:解决饥饿,实际没必要

不公平:当一个线程持有锁,其他线程进入阻塞队列等待,当持有者释放锁时,所有线程开始争夺。

即,后进入的也可能先执行。

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

强行插入,有机会在中间输出

改为公平锁后

ReentrantLock lock = new ReentrantLock(true);

强行插入,总是在最后输出

公平锁一般没有必要,会降低并发度,后面分析原理时会讲解

条件变量

synchronized中也有条件变量,就是waitSet休息室,当条件不满足时进入waitSet等待

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

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

使用要点:

  • await前需要获得锁
  • await执行后,会释放出锁,进入conditionObject等待
  • await的线程被唤醒(或打断,或超时)取重新竞争lock锁。
  • 竞争lock锁成功后,从await后继续执行
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    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.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("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

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

输出

下一篇文章:【并发编程】顺序控制&交替输出abc-CSDN博客

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五敷有你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值