Java锁示例 – ReentrantLock
欢迎来到Java锁示例教程。通常在多线程环境中,为了保证线程安全,我们可以使用synchronized关键字。
Java 锁
大多数情况下,synchronized关键字是首选的方法,但它存在一些缺点,这导致了在Java并发包中包含Lock API。Java 1.5并发API引入了java.util.concurrent.locks包,其中包括Lock接口和一些实现类,以改善对象锁定机制。Java Lock API中的一些重要接口和类有:
-
- 锁:这是锁API的基本接口。它提供了与关键字synchronized相同的所有特性,并提供了创建不同条件进行锁定的附加方法,为线程提供了等待锁的超时时间。其中一些重要的方法有lock()来获取锁,unlock()来释放锁,tryLock()来等待一定时间获取锁,newCondition()用于创建条件等。
条件:条件对象类似于对象等待-通知模型,具有创建不同等待集的附加功能。条件对象始终由锁对象创建。其中一些重要的方法有await(),与wait()类似,signal()和signalAll(),与notify()和notifyAll()方法类似。
读写锁:它包含一对关联锁,一个用于只读操作,另一个用于写操作。只要没有写线程,只读锁可以同时由多个读线程持有。写锁是独占的。
可重入锁:这是最常用的Lock接口的实现类。该类以与synchronized关键字类似的方式实现Lock接口。除了Lock接口的实现,可重入锁还包含一些实用方法,用于获取持有锁的线程以及等待获取锁的线程等。同步块在本质上是可重入的,即如果一个线程对监视器对象持有锁,并且如果另一个同步块需要对同一监视器对象持有锁,则线程可以进入该代码块。我认为这是类名为ReentrantLock的原因。让我们通过一个简单的例子来理解这个特性。
如果一个线程进入foo()方法,它将锁定Test对象,因此当它尝试执行bar()方法时,允许该线程执行bar()方法,因为它已经持有Test对象的锁,即与synchronized(this)相同。
Java锁示例 – Java中的可重入锁
现在让我们来看一个简单的例子,我们将使用Java Lock API来替换synchronized关键字。假设我们有一个Resource类,其中包含一些需要线程安全的操作,同时也有一些不需要线程安全的方法。
package com.Olivia.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接口的类,我们将在这个类中使用资源方法。
package com.Olivia.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 并重新编写上述程序,而不使用 synchronized 关键字。我们将在 Java 中使用 ReentrantLock。
package com.Olivia.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中的锁机制与synchronized的对比
根据上述细节和程序,我们可以轻松得出Java Lock和同步之间的以下区别。
-
- Java的锁API提供了更多的可见性和锁定选项,不同于synchronized关键字,线程有可能无限期地等待锁,我们可以使用tryLock()方法来确保线程只等待特定的时间。
同步代码更清洁易于维护,而使用锁的话,我们被迫使用try-finally块来确保无论在lock()和unlock()方法调用之间是否抛出了异常,锁都被释放。
同步块或方法只能覆盖一个方法,而使用锁API,我们可以在一个方法中获取锁,在另一个方法中释放锁。
synchronized关键字不提供公平性,而我们可以在创建ReentrantLock对象时将公平性设置为true,这样等待时间最长的线程会首先获取锁。
我们可以为锁创建不同的条件,并且不同的线程可以等待不同的条件。
关于Java锁示例,Java中的ReentrantLock以及与synchronized关键字的比较分析就这些内容了。