深入了解Java并发AQS的独占锁模式
独占锁是Java并发编程中重要的一种锁机制,它可以保证共享资源同时只能被一个线程所访问和修改。AQS(AbstractQueuedSynchronizer)是Java中实现锁机制的基础,独占锁模式的实现也是基于AQS的ReentrantLock类。
AQS基本结构
AQS的核心是一个等待队列,其中包含了阻塞的线程,队列采用FIFO的顺序进行管理。其中还有一个同步状态值,它用于判断当前线程是否可能获得锁。
ReentrantLock实现独占锁
ReentrantLock是基于AQS实现独占锁的。它的核心代码如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
protected final boolean tryRelease(int releases) {
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
setState(0);
setExclusiveOwnerThread(null);
return true;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
protected final ConditionObject newCondition() {
return new ConditionObject();
}
}
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
ReentrantLock中有一个Sync类,它是AQS的子类,定义了锁的基本操作方法。其中lock()方法用于获得锁,它首先尝试CAS修改同步状态值,如果成功,则将当前线程设为占有线程;否则调用acquire方法将该线程加入等待队列,实现阻塞并等待获取锁的功能。tryRelease方法用于释放锁,它首先判断当前线程是不是锁的持有者,如果不是,则抛出IllegalMonitorStateException异常,否则就清空同步状态,并将占有线程设为null。isHeldExclusively方法则用于判断当前线程是否占有锁,使用时一定注意,这个方法不是“当前线程是否等待锁”,而是“当前线程是否占有锁”。newCondition方法用于返回一个新的ConditionObject对象。
独占锁模式的示例
示例1:基于ReentrantLock实现简单的互斥
下面的示例演示了基于ReentrantLock实现简单的互斥的过程:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private static Lock lock = new ReentrantLock();
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter++;
lock.unlock();
}).start();
new Thread(() -> {
lock.lock();
counter--;
lock.unlock();
}).start();
Thread.sleep(3000);
System.out.println(counter);
}
}
在上面的代码中,定义了一个ReentrantLock对象,并定义了一个counter变量。在两个线程中分别对该变量进行了加一和减一的操作,并在执行完后释放锁。在最后打印counter的值,我们可以看到它的值保持不变,这是因为两个线程都无法同时对counter变量进行操作。
示例2:基于ReentrantLock实现带有等待时限的锁
下面的示例演示了一个带有等待时限的锁的实现方式:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("Thread A completed");
}).start();
new Thread(() -> {
lock.lock();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
System.out.println("Thread B completed");
}).start();
Thread.sleep(20000);
System.out.println("Main thread completed");
}
}
在上面的代码中,首先定义了一个ReentrantLock对象,然后两个线程分别尝试获取该锁。线程A在获取锁前使用tryLock方法进行了3秒钟的等待,超过此时间则终止等待,如果获取到锁则会进行5秒钟的睡眠操作。线程B则直接通过lock方法获取锁,并在获取到锁后进行10秒钟的睡眠操作。最后在主线程中等待了20秒钟后打印结果,我们可以看到线程A在3秒钟后成功获取锁,并完成了操作;线程B在10秒钟后也完成了操作。在两个线程完成操作后即可释放锁,其他线程就可以获取该锁了。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:深入了解Java并发AQS的独占锁模式 - Python技术站