作者:禅与计算机程序设计艺术
1.简介
什么是锁?
在多线程编程中,多个线程访问共享资源时可能发生冲突,这种冲突往往会导致数据不准确、程序出错甚至崩溃等后果,为了避免这种冲突,我们就需要对共享资源进行保护,以确保每个线程都能独自地运行,并且互斥地访问该资源。而锁就是用来实现资源保护的一种同步机制。锁是一个抽象概念,它是由许多具体实现方式组成的,本文将对Java中的几种锁进行详细介绍。
为什么要使用锁?
在多线程环境下,不同线程间共享同一个资源(变量、内存空间)时,如果没有采取合适的同步措施,则容易出现数据竞争,导致数据错误、程序崩溃等严重后果。为了保证数据的一致性,在多线程访问同一资源时,通常会使用锁(Lock)来协调各个线程之间的访问顺序,从而保证数据的正确性和完整性。
Lock接口及其实现类
java.util.concurrent包提供了四种锁类:ReentrantLock、ReentrantReadWriteLock、StampedLock和Condition对象。下面介绍一下这些类的用法和区别。
ReentrantLock类
ReentrantLock是Java中最基础的同步工具类,是可重入锁。该类提供与synchronized相同的同步功能,但增加了一些高级特性,如:能够响应中断、定时锁等。除了在创建它的线程已经持有锁的情况下,不会阻塞线程;获取锁的方法可以传入公平参数,默认非公平,即按照请求的顺序去获得锁。常用的方法如下:
lock():获取锁。
unlock():释放锁。
newCondition():创建与当前锁绑定的条件对象。
import java.util.concurrent.locks.*; public class MyClass { private final ReentrantLock lock = new ReentrantLock(); public void myMethod(){ lock.lock(); //获取锁 try{ //do something with the locked resource }finally{ lock.unlock(); //释放锁 } } public static void main(String[] args) { MyClass obj = new MyClass(); Thread threadA = new Thread(() -> { obj.myMethod(); }); Thread threadB = new Thread(() -> { obj.myMethod(); }); threadA.start(); threadB.start(); } }
ReadWriteLock读写锁
ReadWriteLock是一个接口,表示一个对象同时允许多个线程进行读操作和写操作,但是读操作不能排斥其他写操作,写操作也不能排斥其他读操作。具体操作如下:
readLock().lock(): 获取读锁。
writeLock().lock(): 获取写锁。
readLock().unlock(): 释放读锁。
writeLock().unlock(): 释放写锁。
getReadHoldCount(): 当前线程正在持有的读锁数量。
isWriteLocked(): 是否被写锁占用。
isWriteLockedByCurrentThread(): 当前线程是否正在占用写锁。
getQueueLength(): 当前等待获取读锁的线程数量。
hasQueuedThreads(): 是否存在等待获取读锁的线程。
waitUntil(): 将调用线程置于等待状态,直到指定的期限时间,如果读锁可用,返回true;否则立即返回false。
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.*;
public class MyClass implements Runnable { private int value = 0; private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock();
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
System.out.println("Getting a read lock.");
r.lock();
try {
TimeUnit.SECONDS.sleep((long)(Math.random()*3));
System.out.println(Thread.currentThread().getName()+" reads "+value);
} catch (InterruptedException e) {} finally {
r.unlock();
}
} else {
System.out.println("Getting a write lock.");
w.lock();
try {
value++;
TimeUnit.SECONDS.sleep((long)(Math.random()*3));
System.out.println(Thread.currentThread().getName()+" increments to "+value);
} catch (InterruptedException e) {} finally {
w.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyClass obj = new MyClass();
Thread t1 = new Thread(obj,"t1");
Thread t2 = new Thread(obj,"t2");
Thread t3 = new Thread(obj,"t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("Final value: " + obj.value);
}
}
### StampedLock类
StampedLock类提供了一种乐观读写锁。相比于ReentrantReadWriteLock和ReadWriteLock,StampedLock对性能进行了优化,提供了一种低延迟和高吞吐量的方式。具体操作如下:
- stamp(): 获取一个时间戳,用来标识读或写操作,如果需要对共享资源进行修改,则在提交修改前先获取锁定。
- validate(long stamp): 检查指定的时间戳是否有效。
- readLock(): 获取读锁。
- tryOptimisticRead(): 以乐观方式读取共享资源,不加锁,不阻塞线程。
- readUnlock(): 释放读锁。
- writeLock(): 获取写锁。
- tryConvertToWriteLock(long stamp): 如果当前线程已经获取了读锁,尝试转换为写锁,成功返回true,失败返回false。
- forceUnlock(): 释放所有锁,并唤醒所有等待线程。
import java.util.Random; import java.util.concurrent.locks.StampedLock;
public class MyClass { private int value = 0; private Random random = new Random(); private StampedLock sl = new StampedLock();
public int getValue() {
long stamp = sl.tryOptimisticRead();
int copy = this.value;
return sl.validate(stamp)? copy : sl.readLock();
}
public boolean updateValue(int newValue) {
long stamp = sl.writeLock();
try {
while (this.value!= oldValue) {
stamp = sl.tryConvertToWriteLock(stamp);
if (!sl.isWriteLocked()) {
throw new IllegalStateException();
}
stamp = sl.writeLock();
}
this.value = newValue;
return true;
} finally {
sl.unlock(stamp);
}
}
public static void main(String[] args) throws InterruptedException {
MyClass obj = new MyClass();
int oldValue = obj.getValue();
if (oldValue >= 100 ||!obj.updateValue(oldValue+1)) {
System.out.println("Failed to increment value.");
System.exit(1);
}
oldValue = obj.getValue();
System.out.println("New Value:" + oldValue);
oldValue = obj.getValue();
System.out.println("Another New Value:" + oldValue);
}
} ```