AQS同步组件--ReentrantLock与锁
2019-05-14 本文已影响0人
三不猴子
ReentrantLock与锁
Synchronized和ReentrantLock异同
- 可重入性:两者都具有可重入性
- 锁的实现:Synchronized是依赖jvm实现的,ReentrantLock是jdk实现的。(我们可以理解为一个是操作系统层面的实现另一个是用户自己自己实现的)Synchronized的实现是jvm层面的很难看到其中的实现。而ReentrantLock是通过jvm实现的我们可以通过阅读jvm源码来查看实现。
- 性能区别:在Synchronized优化之前Synchronized的性能相比ReentrantLock差很多,在Synchronized引入了偏向锁,轻量级锁也就是自旋锁之后了,两者的性能相差不大了。在两者都可用的情况下官方更推荐使用Synchronized,因为其写法更简单,Synchronized的优化就是借鉴了ReentrantLock中的cas技术。
- 功能区别:便利性,很明显synchronized的使用更加便利,ReentrantLock在细粒度和灵活性中会优于Synchronized。
ReentrantLock独有功能
- ReentrantLock可指定是公平锁还是非公平锁,Synchronized只能是非公平锁。(公平锁就是先等待的线程先获得锁)
- ReentrantLock提供一个Condition类,可以分组唤醒需要唤醒的形成。synchronized是要嘛随机唤醒一个线程要嘛唤醒所有的线程。
- ReentrantLock提供了一种能够中断等待锁的线程的机制lock.locInterruptibly(),ReentrantLock实现是一种自旋锁通过循环调用,通过cas机制来实现加锁。性能较好是因为避免了线程进入内核的阻塞状态
@Slf4j
public class LockExample2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
我们首先使用 private final static Lock lock = new ReentrantLock()声明一个所得实例,然后使用
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
进行加锁和解锁操作。
我们在通过一个例子来看看这个ReentrantReadWriteLock怎么用。
@Slf4j
public class LockExample3 {
private final Map<String, Data> map = new TreeMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public Data get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public Set<String> getAllKeys() {
readLock.lock();
try {
return map.keySet();
} finally {
readLock.unlock();
}
}
public Data put(String key, Data value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
readLock.unlock();
}
}
class Data {
}
}
通过 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock()声明一个ReentrantReadWriteLock,然后再分别获取 private final Lock readLock = lock.readLock() private final Lock writeLock = lock.writeLock()读锁和写锁。
我们在这个map读的时候加上读锁在写的时候加上写锁,但是这里有问题就是这个锁是悲观锁,也就是说在执行写锁的时候一定不能有读锁,当读操作特 特别多的时候很有可能会让写锁一直无法执行。
我们看一下官方的例子学习一下,StampedLock
import java.util.concurrent.locks.StampedLock;
public class LockExample4 {
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
//下面看看乐观读锁案例
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
double currentX = x, currentY = y; //将两个字段读入本地局部变量
if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
try {
currentX = x; // 将两个字段读入本地局部变量
currentY = y; // 将两个字段读入本地局部变量
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
//下面是悲观读锁案例
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
if (ws != 0L) { //这是确认转为写锁是否成功
stamp = ws; //如果成功 替换票据
x = newX; //进行状态改变
y = newY; //进行状态改变
break;
} else { //如果不能成功转换为写锁
sl.unlockRead(stamp); //我们显式释放读锁
stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
}
}
} finally {
sl.unlock(stamp); //释放读锁或写锁
}
}
}
}
我们再将前面的里改成StampedLock
@Slf4j
public class LockExample5 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
private final static StampedLock lock = new StampedLock();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
long stamp = lock.writeLock();
try {
count++;
} finally {
lock.unlock(stamp);
}
}
}
这里和之前的不一样的地方就是
long stamp = lock.writeLock();
try {
count++;
} finally {
lock.unlock(stamp);
}
在加锁后会返回一个值,解锁的时候需要传入这个值。