一、概述
-
ReentrantLock 是Java中提供的一种可重入锁(Reentrant Lock),用于在多线程环境下实现对共享资源的互斥访问。与 synchronized 关键字相比,ReentrantLock 提供了更灵活、更强大的功能,同时也更复杂。
-
可重入锁是一种同步机制,它允许同一个线程多次获取同一个锁而不会造成死锁。当线程第一次获得锁后,可以多次重复进入临界区,而不会被阻塞。只有当线程释放了所有重复获得的锁,其他线程才能够获取该锁。当然 synchronized 关键字实现的锁也是可重入的。
-
ReentrantLock 的主要作用如下:
- 互斥访问:ReentrantLock提供了互斥访问的能力,即在同一时刻只允许一个线程获取锁并执行临界区代码,其他线程需要等待锁的释放。
- 可重入性:ReentrantLock 具有可重入性,也就是说同一个线程可以多次获得同一个锁。这意味着线程可以递归地进入由 ReentrantLock 保护的代码块,而不会出现死锁。
- 公平性:ReentrantLock 可以选择提供公平或非公平的锁机制。在公平模式下,锁的获取顺序符合线程的请求顺序;在非公平模式下,锁的获取是无序的,允许插队获取锁,可以提高性能。
- 条件变量支持:ReentrantLock 提供了条件变量(Condition)的支持,可以使用 newCondition() 方法创建一个与该锁关联的条件变量,用于实现更复杂的线程等待/通知机制。
- 锁中断响应:ReentrantLock 支持线程的中断,即当一个线程等待获取锁时,可以通过中断它来提前结束等待。
- 可定时锁等待:ReentrantLock 提供了 tryLock() 和 tryLock(long time, TimeUnit unit) 方法,可以尝试获取锁一段时间,如果在指定时间内未能获取到锁,则返回失败。
- 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(); } } } }