跳转至

ReentrantLock Java:深入理解与高效使用

简介

在多线程编程中,确保共享资源的安全访问至关重要。Java 提供了多种同步机制,ReentrantLock 便是其中强大的一员。它相较于传统的 synchronized 关键字,提供了更灵活、更强大的功能,如公平性选择、可中断的锁获取、多个条件变量等。本文将深入探讨 ReentrantLock 的基础概念、使用方法、常见实践以及最佳实践,帮助读者全面掌握这一重要工具。

目录

  1. 基础概念
    • 什么是 ReentrantLock
    • 与 synchronized 的区别
  2. 使用方法
    • 创建与获取锁
    • 释放锁
    • 锁的公平性
    • 可中断的锁获取
    • 多个条件变量
  3. 常见实践
    • 线程安全的计数器
    • 生产者 - 消费者模型
  4. 最佳实践
    • 合理选择锁的类型
    • 避免死锁
    • 锁的粒度控制
  5. 小结
  6. 参考资料

基础概念

什么是 ReentrantLock

ReentrantLock 是一个可重入的互斥锁,它允许同一个线程多次获取同一个锁。这意味着,当一个线程已经持有了该锁,再次获取时不会被阻塞。这种特性在递归算法或复杂的同步逻辑中非常有用。

与 synchronized 的区别

  • 灵活性ReentrantLock 提供了更多的功能,如可中断的锁获取、公平性选择等,而 synchronized 相对较为简单。
  • 性能:在高竞争环境下,ReentrantLock 可能具有更好的性能,因为它可以使用非阻塞的方式尝试获取锁。
  • 使用场景synchronized 更适合简单的同步场景,而 ReentrantLock 适用于需要更复杂控制的场景。

使用方法

创建与获取锁

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 1 has acquired the lock.");
                // 执行需要同步的代码
            } finally {
                lock.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 2 has acquired the lock.");
                // 执行需要同步的代码
            } finally {
                lock.unlock();
            }
        });

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

在上述代码中,我们创建了一个 ReentrantLock 对象,并在两个线程中获取和释放该锁。注意,获取锁后要在 finally 块中释放锁,以确保即使发生异常,锁也能被正确释放。

释放锁

释放锁的操作必须在获取锁之后进行,并且要确保在所有可能的执行路径上都能正确释放锁。如上述代码中的 finally 块,无论 try 块中的代码是否抛出异常,锁都会被释放。

锁的公平性

ReentrantLock 支持公平性选择,默认情况下是非公平的。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则允许插队,以提高性能。可以通过构造函数来创建公平锁:

ReentrantLock fairLock = new ReentrantLock(true);

可中断的锁获取

ReentrantLock 提供了可中断的锁获取方法 lockInterruptibly()。这意味着在获取锁的过程中,线程可以被中断。

Thread thread3 = new Thread(() -> {
    try {
        lock.lockInterruptibly();
        try {
            System.out.println("Thread 3 has acquired the lock.");
        } finally {
            lock.unlock();
        }
    } catch (InterruptedException e) {
        System.out.println("Thread 3 was interrupted while waiting for the lock.");
    }
});

多个条件变量

ReentrantLock 可以创建多个条件变量,用于线程间的复杂同步。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionExample {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread thread4 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 4 is waiting on the condition.");
                condition.await();
                System.out.println("Thread 4 has been signaled.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread thread5 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 5 is signaling the condition.");
                condition.signal();
            } finally {
                lock.unlock();
            }
        });

        thread4.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread5.start();
    }
}

常见实践

线程安全的计数器

import java.util.concurrent.locks.ReentrantLock;

public class ThreadSafeCounter {
    private int count;
    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();
        }
    }
}

生产者 - 消费者模型

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerExample {
    private static final int MAX_SIZE = 5;
    private int[] buffer = new int[MAX_SIZE];
    private int in = 0;
    private int out = 0;
    private int count = 0;

    private ReentrantLock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public void produce(int item) throws InterruptedException {
        lock.lock();
        try {
            while (count == MAX_SIZE) {
                notFull.await();
            }
            buffer[in] = item;
            in = (in + 1) % MAX_SIZE;
            count++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            int item = buffer[out];
            out = (out + 1) % MAX_SIZE;
            count--;
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

最佳实践

合理选择锁的类型

根据具体的应用场景,选择公平锁或非公平锁。非公平锁通常性能更好,但在某些情况下,公平锁可能更符合需求。

避免死锁

确保锁的获取和释放顺序正确,避免出现两个或多个线程相互等待对方释放锁的情况。

锁的粒度控制

尽量减小锁的粒度,只在必要的代码块上加锁,以提高并发性能。

小结

ReentrantLock 是 Java 多线程编程中一个强大的工具,它提供了比 synchronized 更多的功能和灵活性。通过合理使用 ReentrantLock,可以实现更高效、更安全的多线程程序。在实际应用中,需要根据具体的场景选择合适的锁类型,并注意避免死锁和控制锁的粒度。

参考资料