构建自定义同步工具

一、通过轮询与休眠的方式实现简单的有界缓存

public void put(V v) throws InterruptedException {
while (true) { //轮询
synchronized (this) { //加锁等待
if (!isFull()) { //如果缓存没满 则执行插入
doPut(v);
return;
}
}
Thread.sleep(SLEEP_GRANULARITY); //如果缓存满了 线程等待一段时间后继续轮询
}
} public V take() throws InterruptedException {//同理 相反
while (true) {
synchronized (this) {
if (!isEmpty())
return doTake();
}
Thread.sleep(SLEEP_GRANULARITY);
}
}

二、通过条件队列:wait notify方法

  注意:实际逻辑与上面没区别;且无法通过轮询和休眠方式实现的,也无法通过条件队列实现

  

    // BLOCKS-UNTIL: not-full
public synchronized void put(V v) throws InterruptedException {
while (isFull()) {
wait(); //等待:释放锁且等待;等待通知后获取锁 并继续判断条件谓词
}
doPut(v);
notifyAll(); //通知:通知释放所有等待
}
 
// 问题,不好分析,在put 和 take操作后,通知了所有等待条件队列
// 而且让所有 等待都要去判断 条件谓词;
// 如我put完成,只需要通知take正在等待的,而无需通知put正在等待的(反之亦然),因为只有take后,缓存才有可能不是满的,才要去判断条件谓词
public synchronized V take() throws InterruptedException {
while (isEmpty()) {
wait();
}
V v = doTake();
notifyAll();
return v;
}

三、内置条件队列的使用

   条件谓词:对象在哪个条件下等待

  条件队列:每次唤醒时,必须重新检查条件谓词

四、显式锁Condition:自定义条件队列

  内置条件队列:每个内置锁只能有一个相关联的条件队列

    所以:多个线程在一个条件队列上等待不同的条件谓词不好解决

    方法:wait、notify、notifyAll

  而Condition:每个Lock,可以有任意的Condition

    方法:await、signal、signalAll

优化内置的条件队列:

public class ConditionBoundedBuffer <T> {
protected final Lock lock = new ReentrantLock();
// 条件谓词 未满
private final Condition notFull = lock.newCondition();
// 条件谓词 非空
private final Condition notEmpty = lock.newCondition();
private static final int BUFFER_SIZE = 100;
private final T[] items = (T[]) new Object[BUFFER_SIZE];
private int tail, head, count; // BLOCKS-UNTIL: notFull
public void put(T x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); //当缓存满了,等待有线程取出缓存;等待未满谓词通知(只有take才有可能让缓存不满)
items[tail] = x;
if (++tail == items.length)
tail = 0;
++count;
notEmpty.signal(); //当插入成功,通知释放非空谓词
} finally {
lock.unlock();
}
} // BLOCKS-UNTIL: notEmpty
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); //当缓存为空,等待插入;等待非空谓词通知
T x = items[head];
items[head] = null;
if (++head == items.length)
head = 0;
--count;
notFull.signal(); //当取出一个,通知释放未满谓词
return x;
} finally {
lock.unlock();
}
}
}

五、AQS:AbstractQueuedSynchronizer

  先来看一个例子:

//通过 lock+条件队列实现信号量Semaphore 许可
public class SemaphoreOnLock {
private final Lock lock = new ReentrantLock();
// CONDITION PREDICATE: permitsAvailable (permits > 0)
private final Condition permitsAvailable = lock.newCondition();
private int permits; SemaphoreOnLock(int initialPermits) {
lock.lock();
try {
permits = initialPermits;
} finally {
lock.unlock();
}
} // BLOCKS-UNTIL: permitsAvailable
public void acquire() throws InterruptedException {
lock.lock();
try {
while (permits <= 0) //当许可<0时 等待 许可释放
permitsAvailable.await();
--permits;
} finally {
lock.unlock();
}
} public void release() {
lock.lock();
try {
++permits; //释放许可
permitsAvailable.signal();
} finally {
lock.unlock();
}
}
}

  AQS负责管理同步器类中的状态,它管理了一个整数状态信息;

  如:通过AQS实现简单的闭锁

public class OneShotLatch {
private final Sync sync = new Sync(); public void signal() {
sync.releaseShared(0);
} public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(0);
} private class Sync extends AbstractQueuedSynchronizer { //继承AQS
protected int tryAcquireShared(int ignored) {
// 判断状态
return (getState() == 1) ? 1 : -1;
} protected boolean tryReleaseShared(int ignored) {
//释放状态
setState(1); // 打开闭锁
return true; // 其他线程可以获取该闭锁 }
}
}

小结

  1.最好使用现有的类库来构建状态类:如缓存

  2.如果现有类不能满足功能:如从一个空队列中删除或获取元素会抛出异常,当然也有阻塞等待

    那么使用Lock、内置的条件队列、Condition显式的条件队列、AQS等来构建自己的同步器也是可行的

    内置条件队列与内置锁关联、显式的条件队列与Lock关联

java并发编程(7)构建自定义同步工具及条件队列的更多相关文章

  1. Java并发编程的4个同步辅助类

    Java并发编程的4个同步辅助类(CountDownLatch.CyclicBarrier.Semphore.Phaser) @https://www.cnblogs.com/lizhangyong/ ...

  2. Java并发编程:线程的同步

    Java并发编程:线程的同步 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...

  3. Java并发编程的4个同步辅助类(CountDownLatch、CyclicBarrier、Semaphore、Phaser)

    我在<JDK1.5引入的concurrent包>中,曾经介绍过CountDownLatch.CyclicBarrier两个类,还给出了CountDownLatch的演示案例.这里再系统总结 ...

  4. Java并发编程的4个同步辅助类(CountDownLatch、CyclicBarrier、Semphore、Phaser)

    我在<jdk1.5引入的concurrent包>中,曾经介绍过CountDownLatch.CyclicBarrier两个类,还给出了CountDownLatch的演示案例.这里再系统总结 ...

  5. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  6. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格依照FIFO的队列.他可以确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  7. 《java并发编程实战》读书笔记11--构建自定义的同步工具,条件队列,Condition,AQS

    第14章 构建自定义的同步工具 本章将介绍实现状态依赖性的各种选择,以及在使用平台提供的状态依赖机制时需要遵守的各项规则. 14.1 状态依赖性的管理 对于并发对象上依赖状态的方法,虽然有时候在前提条 ...

  8. Java并发系列[4]----AbstractQueuedSynchronizer源码分析之条件队列

    通过前面三篇的分析,我们深入了解了AbstractQueuedSynchronizer的内部结构和一些设计理念,知道了AbstractQueuedSynchronizer内部维护了一个同步状态和两个排 ...

  9. Java并发编程(八)同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch) 一.为什么会出现同步容器? ...

随机推荐

  1. angular 响应式表单指令

    响应式表单都是以 form开头的指令 第一列指令(不以name结尾)在html模版中,用 [ ] 第二列指令(以name结尾)在html模版中,不用 [ ]

  2. k8s service

    Service也是k8s的最小操作单元,是真实应用服务的抽象 Service通常用来将浮动的资源与后端真实提供服务的容器进行关联 Service对外表现为一个单一的访问接口,外部不需要了解后端的规模与 ...

  3. C语言—第二次作业

    1.本章学习内容 1.1思维导图 1.2本章学习体会即代码量学习体会 1.2.1学习体会 在本章中对循环的内容进行了加深训练,学习了一种解决问题的方法循环嵌套,也学到了伪代码的运用,在描述算法是运用伪 ...

  4. “全栈2019”Java第四章:创建第一个Java程序

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  5. python list 嵌套 dict 按照字典中的单个key进行单级排序 或 按照多个键进行多级排序

    student = [{"no": 1,"score": 90},{"no": 2,"score": 90},{&quo ...

  6. Python数据分析之文本处理词频统计

    1.项目背景: 原本计划着爬某房产网站的数据做点分析, 结果数据太烂了,链家网的数据干净点, 但都是新开楼盘,没有时间维度,分析意义不大. 学习的步伐不能ting,自然语言处理还的go on 2.分析 ...

  7. 【微信小程序】——rpx、px、rem等尺寸间关系浅析

    最近开发微信小程序,在写样式表的时候发现用PX的效果不太理想,而官方文档用rpx来做响应式布局单位,就仔细研究了下,在此做个小总结: 这里先引用官方定义的尺寸单位‘rpx’:可以根据屏幕宽度进行自适应 ...

  8. [Linux] 如何在 Linux 中提取随机数

    如何在 Linux 中提取随机数 一.设备文件 /dev/random & /dev/urandom 字符特殊文件 /dev/random 和 /dev/urandom (存在于Linux 1 ...

  9. nginx配置文件中,location字段里面的root字段和别名alias

    1. location里面的root例子 server{ listen ; server_name www.wzw.com; location /www { root /data/; //设置虚拟主机 ...

  10. python --爬虫基础 --爬取今日头条 使用 requests 库的基本操作, Ajax

    '''思路一: 由于是Ajax的网页,需要先往下划几下看看XHR的内容变化二:分析js中的代码内容三:获取一页中的内容四:获取图片五:保存在本地 使用的库1. requests 网页获取库 2.fro ...