Java多线程之深入理解ReentrantLock
介绍
在Java中,多线程是一项非常重要的编程技能。但是多线程编程中,锁的使用和性能调优一直是让人头痛的问题。为了解决锁的问题,Java提供了许多种不同的锁,其中之一就是 ReentrantLock
。
在本文中,我们将深入探讨 ReentrantLock
的使用,包括:
- 何时需要使用
ReentrantLock
; - 如何使用
ReentrantLock
; ReentrantLock
和synchronized
关键字的比较;ReentrantLock
的高级用法和性能优化。
何时需要使用ReentrantLock
在Java中,如果多个线程同时访问共享资源,那么就可能产生竞态条件(race condition),导致程序出现问题。为了解决这个问题,我们需要使用锁来保护共享资源,以限制只有一个线程可以访问该资源。
Java的内置锁(synchronized关键字)是最常用的锁,但是它有一些缺点:
- 如果一个线程进入了一个synchronized代码块,而另一个线程又想访问这个代码块,那么它就必须等待,直到第一个线程释放锁才能继续执行。这种等待会浪费CPU资源。
- 如果一个线程在执行synchronized代码块时发生异常,那么JVM会自动释放该线程获得的锁。但是,这可能导致其他线程访问共享资源的不一致性。
对于这些问题, ReentrantLock
提供了一些额外的特性,使得它可以更好地处理多线程的竞争条件。
如何使用ReentrantLock
下面是一个使用 ReentrantLock
的简单例子:
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable {
private final ReentrantLock lock = new ReentrantLock();
private int count;
@Override
public void run() {
try {
lock.lock();
for (int i = 0; i < 10000; i++) {
count++;
}
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
在这个例子中,我们创建了一个 MyRunnable
类,它实现了 Runnable
接口,并使用 ReentrantLock
对 count
变量进行加锁。在 run
方法中,我们使用 try-finally 语句块来确保在完成工作后释放锁。在 MyRunnable
类中还有一个 getCount
方法,该方法用于获取 count
的值。
下面是如何在主方法中使用 MyRunnable
类:
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(myRunnable.getCount());
}
在这个例子中,我们创建了两个线程,它们都使用同一个 MyRunnable
实例作为 Runnable
对象,这意味着它们会同时对 count
变量进行修改。我们使用 start
方法开始这两个线程并使用 join
等待它们执行完毕,最后打印 count
的值。
ReentrantLock和synchronized关键字的比较
synchronized
关键字是Java中最常用的锁。虽然 ReentrantLock
提供了更多的特性,但在大多数情况下,synchronized
关键字就已经足够了。以下是 ReentrantLock
和 synchronized
的比较:
- 可重入性:
ReentrantLock
是可重入的,一个线程可以多次获得同一个锁,而synchronized
也是可重入的; - 中断响应:
ReentrantLock
允许我们在等待锁的过程中中断线程,而synchronized
却不支持这个特性; - 公平性:
ReentrantLock
和synchronized
都可以对竞争的线程进行等待队列排序(公平锁),但是ReentrantLock
可以通过构造函数选择是否是公平锁,而synchronized
只支持非公平锁; - 性能:在低竞争情况下,
synchronized
的性能要比ReentrantLock
好;而在高竞争情况下,ReentrantLock
的性能要比synchronized
好。
ReentrantLock的高级用法和性能优化
除了上述功能外, ReentrantLock
还提供了许多高级用法和性能优化,例如:
tryLock
方法:该方法尝试获取锁,如果没有其他线程持有该锁,就可以获取锁并立即返回true,否则就返回false;lockInterruptibly
方法:该方法尝试获取锁,如果没有其他线程持有该锁,就可以获取锁并立即返回true;如果其他线程持有该锁,则当前线程进入等待状态,直到获取锁或者被中断;Condition
接口:该接口提供了更加灵活的线程等待机制,例如等待某个状态的变化、等待超时、等待中断等。
这些高级用法可以帮助我们更好地控制多线程程序的执行,以及提高程序的性能。
示例说明
下面是一个使用 ReentrantLock
进行加锁的示例,假设我们要模拟银行取钱的过程,多个用户同时访问同一个银行账户,取款的金额必须是已经存在于账户余额中的金额,否则就不能取款:
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
private final ReentrantLock lock = new ReentrantLock();
private int balance;
public BankAccount(int balance) {
this.balance = balance;
}
public void withdraw(int amount) {
lock.lock();
try {
if (amount > balance) {
System.out.println("Can't withdraw " + amount + ". Balance is only " + balance);
} else {
Thread.sleep(new Random().nextInt(500)); //模拟取款的时间
balance -= amount;
System.out.println(Thread.currentThread().getName() + " withdrew " + amount + ", balance is now " + balance);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public int getBalance() {
return balance;
}
}
在 BankAccount
类中,我们使用了 ReentrantLock
对 withdraw
方法进行加锁,以确保多个线程同时对账户进行取款时,只有一个线程可以访问该方法。
下面是如何在主方法中模拟多个用户对银行账户进行取款的过程:
public static void main(String[] args) throws InterruptedException {
BankAccount account = new BankAccount(10000);
Runnable withdrawTask = () -> {
for (int i = 0; i < 10; i++) {
account.withdraw(1000);
}
};
Thread t1 = new Thread(withdrawTask, "User1");
Thread t2 = new Thread(withdrawTask, "User2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final balance: " + account.getBalance());
}
在这个例子中,我们创建了两个线程(User1
和 User2
)对银行账户进行取款操作,每个线程都会尝试取款10次,每次取款1000元。我们使用 Thread.sleep()
方法模拟每次取款可能需要的时间,以及其他线程等待的时间。
总结
在多线程编程中,锁的使用和性能调优一直是让人头痛的问题。Java的内置锁(synchronized关键字)是最常用的锁,但是它有一些缺点。为了解决这些问题,Java提供了许多种不同的锁,其中之一就是ReentrantLock
。通过本文的介绍,相信你已经对ReentrantLock
有了更加深入的理解。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java多线程之深入理解ReentrantLock - Python技术站