Java 锁示例 - ReentrantLock
欢迎阅读 Java Lock 示例教程。通常在多线程环境中,我们使用synchronized来保证线程安全。
爪哇锁
大多数情况下,synchronized关键字是可行的方法,但它有一些缺点,导致Java并发包中包含Lock API。Java 1.5并发API提出了带有接口和一些实现类的java.util.concurrent.locks
包Lock
来改进对象锁定机制。Java Lock API中的一些重要接口和类是:
-
Lock:这是 Lock API 的基本接口。它提供了synchronized关键字的所有功能,并提供了创建不同锁定条件的附加方法,为线程等待锁定提供超时。一些重要的方法包括 lock() 获取锁定、unlock() 释放锁定、tryLock() 等待锁定一段时间、newCondition() 创建条件等。
-
条件:条件对象类似于对象等待-通知模型,但具有创建不同等待集的附加功能。条件对象始终由 Lock 对象创建。一些重要方法是类似于 wait() 和 signal() 的 await()、类似于 notify() 和 notifyAll() 方法的 signalAll()。
-
ReadWriteLock:它包含一对关联的锁,一个用于只读操作,另一个用于写入。只要没有写入线程,读取锁就可以同时由多个读取器线程持有。写入锁是独占的。
-
ReentrantLock:这是 Lock 接口使用最广泛的实现类。该类以与 synchronized 关键字类似的方式实现 Lock 接口。除了 Lock 接口实现之外,ReentrantLock 还包含一些实用方法来获取持有锁的线程、等待获取锁的线程等。同步块本质上是可重入的,即,如果一个线程在监视器对象上拥有锁,并且如果另一个同步块需要在同一监视器对象上拥有锁,那么线程可以进入该代码块。我认为这就是类名为 ReentrantLock 的原因。让我们通过一个简单的示例来了解此功能。
public class Test{ public synchronized foo(){ //do something bar(); } public synchronized bar(){ //do some more } }
如果一个线程进入 foo(),则它会锁定 Test 对象,因此当它尝试执行 bar() 方法时,该线程被允许执行 bar() 方法,因为它已经持有 Test 对象上的锁定,即与 synchronized(this) 相同。
Java 锁示例 - Java 中的 ReentrantLock
现在让我们看一个简单的例子,我们将用 Java Lock API 替换synchronized关键字。假设我们有一个 Resource 类,其中有一些操作我们希望它是线程安全的,而有些方法不需要线程安全。
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//do some operation, DB read, write etc
}
public void doLogging(){
//logging, no need for thread safety
}
}
现在假设我们有一个 Runnable 类,我们将在其中使用 Resource 方法。
package com.journaldev.threads.lock;
public class SynchronizedLockExample implements Runnable{
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
请注意,我使用同步块来获取 Resource 对象上的锁。我们可以在类中创建一个虚拟对象并将其用于锁定目的。现在让我们看看如何使用 Java Lock API 并在不使用同步关键字的情况下重写上述程序。我们将在 Java 中使用 ReentrantLock。
package com.journaldev.threads.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//release lock
lock.unlock();
}
resource.doLogging();
}
}
如您所见,我使用 tryLock() 方法来确保我的线程只等待确定的时间,如果它没有获得对象的锁定,它只会记录并退出。另一个需要注意的重要点是使用 try-finally 块来确保即使 doSomething() 方法调用引发任何异常,锁定也会被释放。
Java Lock 与 synchronized
基于以上细节和程序,我们可以很容易地得出Java Lock和同步之间的以下区别。
- Java Lock API 为锁定提供了更多的可见性和选项,与线程可能无限期等待锁定的synchronized不同,我们可以使用tryLock()来确保线程只等待特定的时间。
- 同步代码更加清晰,也更易于维护,而使用 Lock 时,我们必须使用 try-finally 块来确保即使在 lock() 和 unlock() 方法调用之间抛出某些异常,也能释放 Lock。
- 同步块或方法只能覆盖一种方法,而我们可以在一种方法中获取锁,并在另一种方法中使用 Lock API 释放它。
- synchronized 关键字不提供公平性,而我们可以在创建 ReentrantLock 对象时将公平性设置为 true,以便等待时间最长的线程首先获得锁。
- 我们可以为 Lock 创建不同的条件,不同的线程可以针对不同的条件 await()。
以上就是Java Lock示例,java中的ReentrantLock以及与synchronized关键字的比较分析。