Java多线程(6):锁与AQS(下)
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~
之前说过,AQS(抽象队列同步器)是Java锁机制的底层实现。既然它这么优秀,是骡子是马,就拉出来溜溜吧。
首先用重入锁来实现简单的累加,就像这样:
/**
* 用重入锁实现累加
*
* @author 湘王
*/
public class MyLockTest {
private final Lock lock = new ReentrantLock();
private int value;
public int getNext() {
lock.lock();
try {
value++;
} finally {
lock.unlock();
}
return value;
}
public static void main(String[] args) {
MyLockTest myLock = new MyLockTest();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(myLock.getNext());
}
}
}).start();
}
}
}
运行结果显示数据有重复:

这么简单的计算都能出现重复,这肯定是无法接受的。
再用独占锁来试试看:
/**
* 利用AQS实现自定义独占锁
*
* @author 湘王
*/
public class MyExclusiveLock implements Lock {
@Override
public void lock() { } @Override
public void lockInterruptibly() throws InterruptedException { } @Override
public boolean tryLock() {
return false;
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
} @Override
public void unlock() { } @Override
public Condition newCondition() {
return null;
}
}
可以看到,实现lock接口,就需要实现若干自定义的接口。然后以内部类继承AQS的方式,实现排他锁,昨天也说过,AQS中tryAcquire()和tryRelease()是一一对应的,也就是也管获取,一个管释放,所以代码是:
/**
* 内部类继承AQS的方式,实现排他锁
*/
private static class SyncHelper extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -7666580981453962426L; /**
* 第一个线程进来,拿到锁就返回true;后面的线程进来,拿不到锁就返回false
*/
@Override
protected boolean tryAcquire(int arg) {
// 获取资源状态
int state = getState();
if (0 == state) {// 如果没有线程拿到资源的锁
if (compareAndSetState(0, arg)) {
// 保存当前持有同步锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
} else if (Thread.currentThread() == getExclusiveOwnerThread()) {
// 如果当前线程再次进来,state + 1,可重入
// 如果这里没有这个判断,那么程序会卡死
setState(state + arg);
return true;
}
return false;
} /**
* 锁的获取和释放需要一一对应
*/
@Override
protected boolean tryRelease(int arg) {
// 获取资源状态
int state = getState();
// 返回最后一个通过setExclusiveOwnerThread()方法设置过的线程,或者null
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new RuntimeException();
}
setState(state - arg);
if (0 == state) {
setExclusiveOwnerThread(null);
return true;
}
return false;
} protected Condition newCondition() {
return new ConditionObject();
}
}
然后再用AQS实现lock接口的方法:
/**
* 利用AQS实现自定义独占锁
*
* @author 湘王
*/
public class MyExclusiveLock implements Lock {
private final SyncHelper synchepler = new SyncHelper(); @Override
public void lock() {
synchepler.acquire(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
synchepler.acquireInterruptibly(1);
} @Override
public boolean tryLock() {
return synchepler.tryAcquire(1);
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return synchepler.tryAcquireNanos(1, unit.toNanos(time));
} @Override
public void unlock() {
synchepler.release(1);
} @Override
public Condition newCondition() {
return synchepler.newCondition();
} /**
* 内部类继承AQS的方式,实现排他锁
*/
private static class SyncHelper extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -7666580981453962426L; /**
* 第一个线程进来,拿到锁就返回true;后面的线程进来,拿不到锁就返回false
*/
@Override
protected boolean tryAcquire(int arg) {
// 获取资源状态
int state = getState();
if (0 == state) {// 如果没有线程拿到资源的锁
if (compareAndSetState(0, arg)) {
// 保存当前持有同步锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
} else if (Thread.currentThread() == getExclusiveOwnerThread()) {
// 如果当前线程再次进来,state + 1,可重入
// 如果这里没有这个判断,那么程序会卡死
setState(state + arg);
return true;
}
return false;
} /**
* 锁的获取和释放需要一一对应
*/
@Override
protected boolean tryRelease(int arg) {
// 获取资源状态
int state = getState();
// 返回最后一个通过setExclusiveOwnerThread()方法设置过的线程,或者null
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new RuntimeException();
}
setState(state - arg);
if (0 == state) {
setExclusiveOwnerThread(null);
return true;
}
return false;
} protected Condition newCondition() {
return new ConditionObject();
}
}
}
然后再运行测试:
/**
* 实现Lock接口方法并运行排他锁测试
*
* @author 湘王
*/
public class MyExclusiveLockTester {
// 用自定义AQS独占锁实现
private Lock lock = new MyExclusiveLock();
private int value; public int accmulator() {
lock.lock();
try {
++value;
} finally {
lock.unlock();
} return value;
} public static void main(String[] args) throws InterruptedException {
MyExclusiveLockTester test = new MyExclusiveLockTester();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(test.accmulator());
}
}
}).start();
}
}
}
可以看到,结果无论怎么样都不会再重复了。
这个只是简单的累加,接下来用AQS来实现一个实际的生活场景。比如周末带女票或男票去步行街吃饭,这时候人特别多,需要摇号,而且一次只能进去三张号(不按人头算,按叫到的号来算),该怎么实现呢?
可以顺着这个思路:摇号机虽有很多号,但它本质上是个共享资源,很多人可以共享,但是每次共享的数量有限。这其实就是个可以指定数量的共享锁而已。
既然有了思路,那接下来就好办了。
/**
* 利用AQS实现自定义共享锁
*
* @author 湘王
*/
public class MyShareLock implements Lock {
@Override
public void lock() {
} @Override
public void lockInterruptibly() throws InterruptedException {
} @Override
public boolean tryLock() {
return false;
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
} @Override
public void unlock() {
} @Override
public Condition newCondition() {
return null;
}
}
还是一样实现Lock接口,但这次是用AQS实现共享锁。
/**
* 内部类继承AQS实现共享锁
*
*/
private static class SyncHelper extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -7357716912664213942L; /**
* count表示允许几个线程能同时获得锁
*/
public SyncHelper(int count) {
if (count <= 0) {
throw new IllegalArgumentException("锁资源数量必须大于0");
}
// 设置资源总数
setState(count);
} /**
* 一次允许多少个线程进来,允许数量的线程都能拿到锁,其他的线程进入队列
*/
@Override
protected int tryAcquireShared(int acquires) {
// 自旋
for (;;) {
int state = getState();
int remain = state - acquires;
// 判断剩余锁资源是否已小于0或者CAS执行是否成功
if (remain < 0 || compareAndSetState(state, remain)) {
return remain;
}
}
} /**
* 锁资源的获取和释放要一一对应
*/
@Override
protected boolean tryReleaseShared(int releases) {
// 自旋
for (;;) {
// 获取当前state
int current = getState();
// 释放状态state增加releases
int next = current + releases;
if (next < current) {// 溢出
throw new Error("Maximum permit count exceeded");
}
// 通过CAS更新state的值
// 这里不能用setState()
if (compareAndSetState(current, next)) {
return true;
}
}
} protected Condition newCondition() {
return new ConditionObject();
}
}
然后再来改造之前实现的接口:
/**
* 利用AQS实现自定义共享锁
*
* @author 湘王
*/
public class MyShareLock implements Lock {
public static int count;
private final SyncHelper synchepler = new SyncHelper(count); @Override
public void lock() {
synchepler.acquireShared(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
synchepler.acquireSharedInterruptibly(1);
} @Override
public boolean tryLock() {
return synchepler.tryAcquireShared(1) > 0;
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return synchepler.tryAcquireSharedNanos(1, unit.toNanos(time));
} @Override
public void unlock() {
synchepler.releaseShared(1);
} @Override
public Condition newCondition() {
return synchepler.newCondition();
} /**
* 内部类继承AQS实现共享锁
*
*/
private static class SyncHelper extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -7357716912664213942L; /**
* count表示允许几个线程能同时获得锁
*/
public SyncHelper(int count) {
if (count <= 0) {
throw new IllegalArgumentException("锁资源数量必须大于0");
}
// 设置资源总数
setState(count);
} /**
* 一次允许多少个线程进来,允许数量的线程都能拿到锁,其他的线程进入队列
*/
@Override
protected int tryAcquireShared(int acquires) {
// 自旋
for (;;) {
int state = getState();
int remain = state - acquires;
// 判断剩余锁资源是否已小于0或者CAS执行是否成功
if (remain < 0 || compareAndSetState(state, remain)) {
return remain;
}
}
} /**
* 锁资源的获取和释放要一一对应
*/
@Override
protected boolean tryReleaseShared(int releases) {
// 自旋
for (;;) {
// 获取当前state
int current = getState();
// 释放状态state增加releases
int next = current + releases;
if (next < current) {// 溢出
throw new Error("Maximum permit count exceeded");
}
// 通过CAS更新state的值
// 这里不能用setState()
if (compareAndSetState(current, next)) {
return true;
}
}
} protected Condition newCondition() {
return new ConditionObject();
}
}
}
接下来就该测试咱们需要的效果是否能实现了:
public class MyShareLockTester {
public static void main(String[] args) throws InterruptedException {
// 用自定义AQS共享锁实现
// 一次允许发放三把锁
MyShareLock.count = 3;
final Lock lock = new MyShareLock();
// 模拟20个客户端访问
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("持有 " + Thread.currentThread().getName() + " 的客人可以进餐厅就餐");
// 每两次叫号之间间隔一段时间,模拟真实场景
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 使用完成释放锁
lock.unlock();
}
}
}).start();
}
}
}
这里有20个号,每次只能发放3张,运行之后就可以看到确实如此。
AQS是个很神奇也很好玩的东西,就像它的作者(也是除了高司令就是对Java影响最大的那个人,整个Java的多线程juc包代码就是他编写的)Doug Lea在AbstractQueuedSynchronizer的注释中所说:AQS只是一个框架,至于怎么玩,就是你的事了!
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~
Java多线程(6):锁与AQS(下)的更多相关文章
- Java 多线程:锁(三)
Java 多线程:锁(三) 作者:Grey 原文地址: 博客园:Java 多线程:锁(三) CSDN:Java 多线程:锁(三) StampedLock StampedLock其实是对读写锁的一种改进 ...
- JAVA多线程与锁机制
JAVA多线程与锁机制 1 关于Synchronized和lock synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码 ...
- Java 多线程:锁(一)
Java 多线程:锁(一) 作者:Grey 原文地址: 博客园:Java 多线程:锁(一) CSDN:Java 多线程:锁(一) CAS 比较与交换的意思 举个例子,内存有个值是 3,如果用 Java ...
- Java 多线程:锁(二)
Java 多线程:锁(二) 作者:Grey 原文地址: 博客园:Java 多线程:锁(二) CSDN:Java 多线程:锁(二) AtomicLong VS LongAddr VS Synchroni ...
- java多线程之锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁
转载至:https://blog.csdn.net/zqz_zqz/article/details/70233767 之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比 ...
- java多线程----悲观锁与乐观锁
java多线程中悲观锁与乐观锁思想 一.悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线 ...
- (转)java 多线程 对象锁&类锁
转自:http://blog.csdn.net/u013142781/article/details/51697672 最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不 ...
- Java多线程--公平锁与非公平锁
上一篇文章介绍了AQS的基本原理,它其实就是一个并发包的基础组件,用来实现各种锁,各种同步组件的.它包含了state变量.加锁线程.等待队列等并发中的核心组件,现在我们来看一下多线程获取锁的顺序问题. ...
- Java多线程系列——锁的那些事
引入 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率. 下面先带大家来总体预览一下锁的分类图 java锁的具体实现类 1.乐观锁 VS 悲观锁 乐观锁与悲观锁是 ...
- java多线程编程——锁优化
并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问.加锁会带来性能上的损坏,似乎是众所周知的事情.然而,加锁本身不会带来多少的性能消耗,性能主要是在线程的获取锁的过程.如 ...
随机推荐
- 如何免费申请js.org二级域名
最近看到很多人都去申请了js.org的域名,我就来写个教程吧! (本教程只注重于申请域名,而不是如何使用Github) 看看成品:https://butterfly.js.org/ 官网是这么写的: ...
- NOI P序列题 (二分)
题面 题解 --WQS二分 想到这个这题就完了. 赛时没想到这个你就完了. 时间复杂度 O ( n log a ) O(n\log a) O(nloga) 不难发现这题有凸性,可以WQS二分. 我 ...
- 说说 JSON 格式的弊端与解决方法
JSON 格式是目前最流行的数据交互格式,广泛应用于前后端分离的系统.但也有一些场合不适合使用 JSON 格式. 1 JSON 格式弊端 有这样的一个需求:希望把客户端的日志上传到服务器存储起来.原先 ...
- luogu P2882 [USACO07MAR]Face The Right Way G
题目描述 Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing forwar ...
- .NET 反向代理-YARP
什么是 YARP YARP (另一个反向代理) 设计为一个库,提供核心代理功能,你可以根据应用程序的特定需求进行自定义. YARP 是使用 .NET的基础架构构建在 .NET上的.YARP 的主要不同 ...
- day05-线程的应用04
7.线程的应用03 7.4坦克大战5.0版 增加功能: 我方坦克在发射的子弹消亡之后,才能发射新的子弹==>拓展:发射多颗子弹怎么办,控制一次最多只能发射5颗子弹 让敌人坦克发射的子弹消亡之后, ...
- C#winform中使用Cef的ChromiumWebBrowser内嵌谷歌内核,调用前端js方法
1.在winform中调用js方法: --调js中的方法无 入参形式 webBrowser1.ExecuteJavascript("logout()"); --调js中的方法给js ...
- 2020年12月-第02阶段-前端基础-CSS Day06
CSS Day06 定位(position) 理解 能说出为什么要用定位 能说出定位的4种分类 能说出四种定位的各自特点 能说出我们为什么常用子绝父相布局 应用 能写出淘宝轮播图布局 1. CSS 布 ...
- 编写 bzt 脚本的正确姿势
这是今年1月24日的旧文,发现没在这里发过,就搬运过来了. 声明 本文讨论的使用场景主要为使用已有的 jmx 脚本,并配合 json 对 jmx 脚本进行部分参数的动态修改. 只补充一些官方文档上没有 ...
- dotnet7 aot编译实战
0 起因 这段日子看到dotnet7-rc1发布,我对NativeAot功能比较感兴趣,如果aot成功,这意味了我们的dotnet程序在防破解的上直接指数级提高.我随手使用asp.netcore-7. ...