Java锁示例 – ReentrantLock

欢迎来到Java锁示例教程。通常在多线程环境中,为了保证线程安全,我们可以使用synchronized关键字。

Java 锁

大多数情况下,synchronized关键字是首选的方法,但它存在一些缺点,这导致了在Java并发包中包含Lock API。Java 1.5并发API引入了java.util.concurrent.locks包,其中包括Lock接口和一些实现类,以改善对象锁定机制。Java Lock API中的一些重要接口和类有:

    1. 锁:这是锁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和同步之间的以下区别。

    1. Java的锁API提供了更多的可见性和锁定选项,不同于synchronized关键字,线程有可能无限期地等待锁,我们可以使用tryLock()方法来确保线程只等待特定的时间。

同步代码更清洁易于维护,而使用锁的话,我们被迫使用try-finally块来确保无论在lock()和unlock()方法调用之间是否抛出了异常,锁都被释放。

同步块或方法只能覆盖一个方法,而使用锁API,我们可以在一个方法中获取锁,在另一个方法中释放锁。

synchronized关键字不提供公平性,而我们可以在创建ReentrantLock对象时将公平性设置为true,这样等待时间最长的线程会首先获取锁。

我们可以为锁创建不同的条件,并且不同的线程可以等待不同的条件。

关于Java锁示例,Java中的ReentrantLock以及与synchronized关键字的比较分析就这些内容了。

发表回复 0

Your email address will not be published. Required fields are marked *


广告
将在 10 秒后关闭
bannerAds