Java 多线程之 ReentrantLock(可重入锁/公平锁/非公平锁)

一、概述

  • ReentrantLock 是Java中提供的一种可重入锁(Reentrant Lock),用于在多线程环境下实现对共享资源的互斥访问。与 synchronized 关键字相比,ReentrantLock 提供了更灵活、更强大的功能,同时也更复杂。

  • 可重入锁是一种同步机制,它允许同一个线程多次获取同一个锁而不会造成死锁。当线程第一次获得锁后,可以多次重复进入临界区,而不会被阻塞。只有当线程释放了所有重复获得的锁,其他线程才能够获取该锁。当然 synchronized 关键字实现的锁也是可重入的。

  • ReentrantLock 的主要作用如下:

    1. 互斥访问:ReentrantLock提供了互斥访问的能力,即在同一时刻只允许一个线程获取锁并执行临界区代码,其他线程需要等待锁的释放。
    2. 可重入性:ReentrantLock 具有可重入性,也就是说同一个线程可以多次获得同一个锁。这意味着线程可以递归地进入由 ReentrantLock 保护的代码块,而不会出现死锁。
    3. 公平性:ReentrantLock 可以选择提供公平或非公平的锁机制。在公平模式下,锁的获取顺序符合线程的请求顺序;在非公平模式下,锁的获取是无序的,允许插队获取锁,可以提高性能。
    4. 条件变量支持:ReentrantLock 提供了条件变量(Condition)的支持,可以使用 newCondition() 方法创建一个与该锁关联的条件变量,用于实现更复杂的线程等待/通知机制。
    5. 锁中断响应:ReentrantLock 支持线程的中断,即当一个线程等待获取锁时,可以通过中断它来提前结束等待。
    6. 可定时锁等待:ReentrantLock 提供了 tryLock() 和 tryLock(long time, TimeUnit unit) 方法,可以尝试获取锁一段时间,如果在指定时间内未能获取到锁,则返回失败。
    7. API 控制:与 synchronized 不同,ReentrantLock 提供了更多的灵活性和控制。例如,可以通过 lockInterruptibly() 方法实现可中断的加锁操作,还可以通过 getHoldCount() 方法获取当前线程持有该锁的次数。

二、使用方法

2.1 公平锁/非公平锁

  • 直接使用 ReentrantLock 的 lock 和 unlcok 方法,基本功能同 synchronized 关键字。但是他比 synchronized 强大的地方在于他可以设置为公平锁和非公平锁,以及使用可中断获取锁和超时获取锁方法。

    class XXXXXX {
        private ReentrantLock lock = new ReentrantLock(true); // 公平锁
        //private ReentrantLock lock = new ReentrantLock(false); // 非公平锁
        public void increment() {
            lock.lock(); // 获取锁
            // lock.lockInterruptibly(); // 获取可中断的锁
            // lock.tryLock(3000, TimeUnit.SECONDS);// 在指时间内获取锁
            try {
                // 业务逻辑
            } finally {
                lock.unlock();// 释放锁
            }
        }
    }
    

    lockInterruptibly() 方法说明:

    • 如果当前线程未被中断,并且锁是可用的,则该方法立即获取锁并返回。

    • 如果当前线程未被中断,但是锁当前被其他线程占用,则当前线程进入阻塞状态,等待锁的释放。

    • 如果当前线程在等待锁的过程中被中断,则抛出 InterruptedException 异常。

2.2 条件变量 (Condition)

  • ReentrantLock 的条件变量(Condition)的使用,实现线程等待/通知机制。

    class BoundedQueue<T> {
        private Queue<T> queue = new LinkedList<>();
        private int capacity;
        private ReentrantLock lock = new ReentrantLock();
        private Condition notFull = lock.newCondition();
        private Condition notEmpty = lock.newCondition();
    
        public BoundedQueue(int capacity) {
            this.capacity = capacity;
        }
    
        public void enqueue(T item) throws InterruptedException {
            lock.lock();
            try {
                while (queue.size() == capacity) {
                    // 条件1达到, 业务逻辑1等待
                    notFull.await();
                }
                queue.add(item);
                // 通知业务逻辑2执行
                notEmpty.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
        public T dequeue() throws InterruptedException {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    // 条件2达到,业务逻辑2等等
                    notEmpty.await();
                }
                T item = queue.poll();
                // 通知业务逻辑1执行
                notFull.signalAll();
                return item;
            } finally {
                lock.unlock();
            }
        }
    }
    

三、测试示例

3.1 线程安全的计数器示例

  • 定义一个Counter类,用于在多线程环境下进行计数。使用ReentrantLock来保护count变量的访问。increment()方法和getCount()方法获取锁来确保对count的操作是线程安全的。

    class Counter {
        private int count = 0;
        private ReentrantLock lock = new ReentrantLock();
    
        public void increment() {
            lock.lock();
            try {
                count++;
            } finally {
                lock.unlock();
            }
        }
    
        public int getCount() {
            lock.lock();
            try {
                return count;
            } finally {
                lock.unlock();
            }
        }
    }
    
  • 测试方法:开启20个线程同时计数,在多线程条件下正确结果为20000。

        private void testCounter(){
            Counter counter = new Counter();
            List<Thread> threads = new ArrayList();
            for(int i = 0; i< 20; i++){
                Thread t = new Thread(()->{
                    for(int j=0; j< 1000; j++){
                        counter.increment();
                    }
                });
                threads.add(t);
            }
    
            for(Thread t : threads){
                t.start();
            }
    
            for(Thread t : threads){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            System.out.println("最终结果:"+counter.getCount());
        }
    
    

3.2 有界队列示例

  • BoundedQueue类实现了一个有界队列,使用ReentrantLock和条件变量来实现线程安全的入队和出队操作。当队列已满时,入队线程会等待notFull条件成立;当队列为空时,出队线程会等待notEmpty条件成立。

    class BoundedQueue<T> {
        private Queue<T> queue = new LinkedList<>();
        private int capacity;
        private ReentrantLock lock = new ReentrantLock();
        private Condition notFull = lock.newCondition();
        private Condition notEmpty = lock.newCondition();
    
        public BoundedQueue(int capacity) {
            this.capacity = capacity;
        }
    
        public void enqueue(T item) throws InterruptedException {
            lock.lock();
            try {
                while (queue.size() == capacity) {
                    notFull.await();
                }
                queue.add(item);
                notEmpty.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
        public T dequeue() throws InterruptedException {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    notEmpty.await();
                }
                T item = queue.poll();
                notFull.signalAll();
                return item;
            } finally {
                lock.unlock();
            }
        }
    }
    
  • 测试方法:创建一组生产线程,一组消费线程。不管生产线程和消费线程的数量如何变化,以及生过程和消费过程业务用时长短(用sleep模拟),他们总能保持生产一定数量后消费一定数量,不会一直生产,也不会一直消费。

        private void testCondition(){
            BoundedQueue<String> queue = new BoundedQueue<String>(10);
    
            List<Thread> threads = new ArrayList();
            for(int i = 1; i<= 5; i++){
                Thread t = new Thread(()->{
                    for(int j=0; j< 100000; j++){
                        try {
                            String text = Thread.currentThread().getName()+" 生产"+j;
                            queue.enqueue(text);
                            System.out.println(text);
    
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.setName("生产线程"+i);
                threads.add(t);
            }
            for(int i = 1; i<= 10; i++){
                Thread t = new Thread(()->{
                    for(int j=0; j< 100000; j++){
                        try {
                            String result = queue.dequeue();
                            System.out.println(Thread.currentThread().getName()+" 消费 "+result);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.setName("消费线程"+i);
                threads.add(t);
            }
    
            for(Thread t : threads){
                t.start();
            }
    
            for(Thread t : threads){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    

四、完整测试代码

  • 完整测试代码如下

    package top.yiqifu.study.p004_thread;
    
    import java.util.*;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test061_ReentrantLock {
    
        public static void main(String[] args) {
            Test061_ReentrantLock test = new Test061_ReentrantLock();
            test.testCounter();
            test.testCondition();
        }
    
        private void testCounter(){
            Counter counter = new Counter();
            List<Thread> threads = new ArrayList();
            for(int i = 0; i< 20; i++){
                Thread t = new Thread(()->{
                    for(int j=0; j< 1000; j++){
                        counter.increment();
                    }
                });
                threads.add(t);
            }
    
            for(Thread t : threads){
                t.start();
            }
    
            for(Thread t : threads){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            System.out.println("最终结果:"+counter.getCount());
        }
    
        private void testCondition(){
            BoundedQueue<String> queue = new BoundedQueue<String>(10);
    
            List<Thread> threads = new ArrayList();
            for(int i = 1; i<= 5; i++){
                Thread t = new Thread(()->{
                    for(int j=0; j< 100000; j++){
                        try {
                            String text = Thread.currentThread().getName()+" 生产"+j;
                            queue.enqueue(text);
                            System.out.println(text);
    
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.setName("生产线程"+i);
                threads.add(t);
            }
            for(int i = 1; i<= 10; i++){
                Thread t = new Thread(()->{
                    for(int j=0; j< 100000; j++){
                        try {
                            String result = queue.dequeue();
                            System.out.println(Thread.currentThread().getName()+" 消费 "+result);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.setName("消费线程"+i);
                threads.add(t);
            }
    
            for(Thread t : threads){
                t.start();
            }
    
            for(Thread t : threads){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
    
        class Counter {
            private int count = 0;
            private ReentrantLock lock = new ReentrantLock();
    
            public void increment() {
                lock.lock();
                try {
                    count++;
                } finally {
                    lock.unlock();
                }
            }
    
            public int getCount() {
                lock.lock();
                try {
                    return count;
                } finally {
                    lock.unlock();
                }
            }
        }
    
        class BoundedQueue<T> {
            private Queue<T> queue = new LinkedList<>();
            private int capacity;
            private ReentrantLock lock = new ReentrantLock();
            private Condition notFull = lock.newCondition();
            private Condition notEmpty = lock.newCondition();
    
            public BoundedQueue(int capacity) {
                this.capacity = capacity;
            }
    
            public void enqueue(T item) throws InterruptedException {
                lock.lock();
                try {
                    while (queue.size() == capacity) {
                        notFull.await();
                    }
                    queue.add(item);
                    notEmpty.signalAll();
                } finally {
                    lock.unlock();
                }
            }
    
            public T dequeue() throws InterruptedException {
                lock.lock();
                try {
                    while (queue.isEmpty()) {
                        notEmpty.await();
                    }
                    T item = queue.poll();
                    notFull.signalAll();
                    return item;
                } finally {
                    lock.unlock();
                }
            }
        }
    
    
    }
    
    
  • 40
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

QIFU

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

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

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

打赏作者

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

抵扣说明:

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

余额充值