并发编程-ReentrentLock概念及使用

1.ReentrantLock概念

ReentrantLock 是 Java 中的一种可重入锁,属于 java.util.concurrent.locks 包。它提供了比 synchronized 关键字更灵活的锁机制,允许更复杂的线程同步控制。以下是 ReentrantLock 的一些关键概念和特点:

  • 可重入性:同一个线程可以多次获得同一把锁,而不会导致死锁。这意味着如果一个线程已经持有了锁,它可以再次获得该锁而不被阻塞。

  • 显式锁定:与 synchronized 不同,ReentrantLock 需要手动锁定和解锁。使用 lock() 方法来锁定,使用 unlock() 方法来解锁。

  • 公平锁与非公平锁ReentrantLock 可以选择是否使用公平性策略。公平锁按照请求锁的顺序分配锁,而非公平锁则不保证顺序,可能会导致某些线程饥饿。

  • 条件变量ReentrantLock 允许使用条件变量(Condition),可以实现更复杂的线程通信机制。通过 newCondition() 方法创建条件变量,可以使用 await()signal() 等方法进行线程之间的协调。

  • 性能:在某些情况下,ReentrantLock 的性能可能优于 synchronized,尤其是在高竞争的场景中,因为它采用了更复杂的算法来管理锁的获取和释放(可以进行更低粒度锁的控制)。

2.基本使用Demo

2.1 lock、unlock方法-Demo

public class ReentrantLockDemo01 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();// 默认非公平锁
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock.lock();
                System.out.println(Thread.currentThread().getName() + " 成功获取锁");
                try {
                    System.out.println(Thread.currentThread().getName() + ".....");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                reentrantLock.unlock();
                System.out.println(Thread.currentThread().getName() + " 释放锁");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);// 确保thread1先执行,thread1进行锁的获取和释放
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " 尝试获取锁");
                reentrantLock.lock();
                System.out.println(Thread.currentThread().getName() + " 成功获取锁");
                try {
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + ".....");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " 释放锁");
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
        System.out.println(Thread.currentThread().getName() + " 执行了");
    }
}

输出:

Thread-0首先获取锁,然后模拟执行2s业务,之后释放锁;

Thread-1首先睡眠1s,然后获取锁,但是被阻塞;等Thread-0释放锁后,Thread-1成功获取锁,执行业务逻辑。

 2.2 可重入性-Demo

public class ReentrantLockDemo02 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();// 默认非公平锁
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock.lock();
                try{
                    System.out.println(Thread.currentThread().getName() + " start");

                    reentrantLock.lock();// 可重入
                    System.out.println();
                    System.out.println(Thread.currentThread().getName() + " reStart");

                    System.out.println(Thread.currentThread().getName() + " end");
                }finally {
                    reentrantLock.unlock();
                }

            }
        });
        
        thread1.start();

        thread1.join();
        System.out.println(Thread.currentThread().getName() + " 执行了");
    }
}

输出:

2.3 公平及非公平锁

默认为非公平锁(ReentrentLock的构成方法)

2.4 可中断性-Demo

public class ReentrantLockWithInterruptorDemo {
    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();

        // t1先获取锁
        Thread t1 = new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + " 启动了");
                reentrantLock.lock();
                System.out.println(Thread.currentThread().getName() + " 获取到锁");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    System.out.println("t1报错");
                    throw new RuntimeException(e);
                }
            } finally {
                reentrantLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 释放锁");
        });

        t1.start();

        // 测试重点: t2后获取锁。测试lock、lockInterruptibly方法
        Thread t2 = new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + " 启动了");
                // 确保t2先执行性
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("t2报错");
                throw new RuntimeException(e);
            }
            try {
                long l = System.currentTimeMillis();
                // reentrantLock.lock();
                reentrantLock.lockInterruptibly();
                long l1 = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + " 等待获取锁的时间"+(l1-l));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败,t2需要中断,t2要退出啦");
                throw new RuntimeException(e);
            } finally {
                reentrantLock.unlock();
            }
        });
        t2.start();

        Thread t3 = new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + " 启动了");
                Thread.sleep(2000);
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + " 报错");
                throw new RuntimeException(e);
            }
            t2.interrupt();// 中断t2
            System.out.println("t3中断t2了");
        });
        t3.start();
    }
}

输出:在Thread-1中,调用reentrantLock.lockInterruptibly();,lockInterruptibly表示该线程如果获取锁失败,则接收中断。在Thread-2中,主动中断Thread-1,所以Thread-1报出异常。

2.5 Condition条件变量-Demo

ReentrantLock 允许使用条件变量(Condition),可以实现更复杂的线程通信机制。通过 newCondition() 方法创建条件变量,可以使用 await()signal() 等方法进行线程之间的协调。

通过Condition模拟实现生产者-消费者模型:

仓库类:

/**
 * 仓库类
 */
public class Depot {
    private int capacity;    // 仓库的容量
    private int size;        // 仓库的实际数量
    private ReentrantLock lock;        // 可重入锁
    private Condition fullCondtion;    // 生产条件
    private Condition emptyCondtion;   // 消费条件

    /**
     * 初始化
     */
    public Depot() {
        capacity = 100;
        size = 0;
        lock = new ReentrantLock();
        fullCondtion = lock.newCondition();
        emptyCondtion = lock.newCondition();
    }

    /**
     * 生产流程
     * @param val:生产的个数
     */
    public void produce(int val) {
        try {
            lock.lock();
            System.out.println("生产者:" + Thread.currentThread().getName() + "开始执行");
            //left:还需生产的个数
            int left = val;
            while (left > 0) {
                //当仓库满了,需要暂停生产
                if (size >= capacity) {
                    System.out.println(Thread.currentThread().getName() + ":当前仓库已满,生产进程进入等待");
                    //当前生产进程阻塞
                    fullCondtion.await();
                }
                // incr:此次生产的数量
                int incr = (capacity - size) > left ? left : (capacity - size);
                size = size + incr;
                left = left - incr;
                System.out.println(Thread.currentThread().getName() + "生产:incr=" + incr + ",left=" + left + ",size=" + size);
                // 生产完了,唤起消费进程
                emptyCondtion.signal();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    /**
     * 消费流程
     *
     * @param val:消费的个数
     */
    public void consume(int val) {
        try {
            lock.lock();
            System.out.println("消费者:" + Thread.currentThread().getName() + "开始执行");
            //left:剩余消费的数量
            int left = val;
            while (left > 0) {
                if (size <= 0) {
                    System.out.println(Thread.currentThread().getName() + ":当前仓库已空,消费进程进入等待");
                    emptyCondtion.await();
                }
                // dec : 此次消费的个数
                int dec = size > left ? left : size;
                left = left - dec;
                size = size - dec;
                System.out.println(Thread.currentThread().getName() + "消费:dec=" + dec + ",left=" + left + ",size=" + size);
                // 唤醒生产进程
                fullCondtion.signal();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

 生产者:

/**
 * 生产者
 */
public class Producer {
    private Depot depot;

    public Producer(Depot depot){
        this.depot= depot;
    }

    public void produce(int val){
        new Thread(() -> {
            depot.produce(val);
        }).start();
    }
}

消费者:

/**
 * 消费者
 */
public class Consumer {
    private Depot depot;

    public Consumer(Depot depot){
        this.depot = depot;
    }

    public void consume(int val){
        new Thread(){
            @Override
            public void run() {
               depot.consume(val);
            }
        }.start();
    }
}

测试类:

public class Main {
    public static void main(String[] args) {
        Depot depot = new Depot();
        Producer producer = new Producer(depot);
        Consumer consumer = new Consumer(depot);

        consumer.consume(150);
        producer.produce(200);
        producer.produce(100);
        consumer.consume(150);
        consumer.consume(50);
        producer.produce(200);
    }
}

结果输出:

 说明:在仓库类中定义生产者和消费者共用的ReentrentLock及Condition。当调用消费者的时候,首先竞争Lock,然后调用Condition.await方法竞争Condition资源,进行消费,当仓库产品为空时,唤醒生产者,消费者释放锁。生产者首先竞争Lock,然后调用Condition.await()方法竞争,开始生产产品,当仓库为满时,通过Condition.signal()唤醒消费者进行消费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值