java并发编程实战:第十四章----构建自定义的同步工具

一、状态依赖性管理

  • 对于单线程程序,某个条件为假,那么这个条件将永远无法成真
  • 在并发程序中,基于状态的条件可能会由于其他线程的操作而改变
     1 可阻塞的状态依赖操作的结构
     2 
     3 acquire lock on object state
     4 while (precondition does not hold) 
     5 {
     6     release lock
     7     wait until precondition might hold
     8     optionally fail if interrupted or timeout expires
     9     reacquire lock
    10 }
    11 perform action
    12 release lock

     

 1 //有界缓存实现的基类2 public abstract class BaseBoundedBuffer<V> {3     private final V[] buf;4     private int tail;5     private int head;6     private int count;7     8     protected BaseBoundedBuffer(int capacity){9         this.buf = (V[]) new Object[capacity];
10     }
11     
12     protected synchronized final void doPut(V v){
13         buf[tail] = v;
14         if (++tail == buf.length){
15             tail = 0;
16         }
17         ++count;
18     }
19     
20     protected synchronized final V doTake(){
21         V v = buf[head];
22         buf[head] = null; //let gc collect
23         if (++head == buf.length){
24             head = 0;
25         }
26         --count;
27         return v;
28     }
29     
30     public synchronized final boolean isFull(){
31         return count == buf.length;
32     }
33     
34     public synchronized final boolean isEmpty(){
35         return count == 0;
36     }
37 }

1、示例:将前提条件的失败传递给调用者 

 1 public class GrumyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
 2     public GrumyBoundedBuffer(int size){
 3         super(size);
 4     }
 5     
 6     public synchronized void put(V v){
 7         if (isFull()){
 8             throw new BufferFullException();
 9         }
10         doPut(v);
11     }
12     
13     public synchronized V take(){
14         if (isEmpty())
15             throw new BufferEmptyExeption();
16         return doTake();
17     }
18 }
19 
20 当不满足前提条件时,有界缓存不会执行相应的操作

 

缺点:已满情况不应为异常;调用者自行处理失败;sleep:降低响应性;自旋等待:浪费CPU;yield让出CPU

2、示例:通过轮询与休眠来实现简单的阻塞

 1 public class SleepyBounedBuffer<V> extends BaseBoundedBuffer<V> {
 2     private static long SLEEP_TIME;
 3     public SleepyBounedBuffer(int size) {
 4         super(size);
 5     }
 6 
 7     public void put(V v) throws InterruptedException{
 8         while (true){
 9             synchronized(this){
10                 if (!isFull()){
11                     doPut(v);
12                     return;
13                 }
14             }
15             Thread.sleep(SLEEP_TIME);
16         }
17     }
18     
19     public V take() throws InterruptedException{
20         while (true){
21             synchronized(this){
22                 if (!isEmpty()){
23                     return doTake();
24                 }
25             }
26             Thread.sleep(SLEEP_TIME);
27         }
28     }
29 }
30 
31 “轮询与休眠“重试机制

 

优点:对于调用者,无需处理失败与异常,操作可阻塞,可中断(休眠时候不要持有锁)

缺点:对于休眠时间设置的权衡(响应性与CPU资源)

3、条件队列——使得一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件变成真(元素是一个个正在等待相关条件的线程)

  • 每个对象都可以作为一个条件队列(API:wait、notify和notifyAll)
    • Object.wait会自动释放锁,并请求操作系统挂起当前线程,从而使其他线程能够获得这个锁并且修改对象的状态
    • Object.notify/notifyAll通知被挂起的线程可以重新请求资源执行
  • 只有能对状态进行检查时,才能在某个条件上等待,并且只有能修改状态时,才能从条件等待中释放另一个线程
  • 条件队列在CPU效率、上下文切换开销和响应性等进行了优化
  • 如果某个功能无法通过“轮询和休眠”来实现,那么使用条件队列也无法实现
 1 public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {2 3     public BoundedBuffer(int capacity) {4         super(capacity);5     }6     7     public synchronized void put(V v) throws InterruptedException{8         while (isFull()){9             wait();
10         }
11         doPut(v);
12         notifyAll();
13     }
14     
15     public synchronized V take() throws InterruptedException{
16         while (isEmpty()){
17             wait();
18         }
19         V v = doTake();
20         notifyAll();
21         return v;
22     }
23 }

 

二、使用条件队列

1、条件谓词

  • 条件等待中存在一种重要的三元关系,包括加锁、wait方法和一个条件谓词
  • 条件谓词是由类中各个状态变量构成的表达式(while)
  • 在测试条件谓词之前必须先持有这个锁
  • 锁对象与条件队列对象(即调用wait和notify等方法所在的对象)必须是同一个对象
  • wait被唤醒后需要重新获得锁,并重新检查条件谓词

2、过早唤醒——一个条件队列与多个条件谓词相关时,wait方法返回不一定线程所等待的条件谓词就变为真了

1 void stateDependentMethod() throws InterruptedException
2 {
3   synchronized(lock)  // 必须通过一个锁来保护条件谓词
4     {
5         while(!condietionPredicate()) 
6             lock.wait();
7     }
8 }

当使用条件等待时(如Object.wait(), 或Condition.await()):

  • 通常都有一个条件谓词--包括一些对象状态的测试,线程在执行前必须首先通过这些测试
  • 在调用wait之前测试条件谓词,并且从wait中返回时再次进行测试
  • 在一个循环中调用wait
  • 确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量
  • 当调用wait, notify或notifyAll等方法时,一定要持有与条件队列相关的锁
  • 在检查条件谓词之后以及开始执行相应的操作之前,不要释放锁。

3、丢失信号量——线程必须等待一个已经为真的条件,但在开始等待之前没有检查条件谓词

如果线程A通知了一个条件队列,而线程B随后在这个条件队列上等待,那么线程B将不会立即醒来,而是需要另一个通知来唤醒它(导致活跃性下降)

4、通知——确保在条件谓词变为真时通过某种方式发出通知挂起的线程

  • 发出通知的线程持有锁调用notify和notifyAll,发出通知后应尽快释放锁
  • 多个线程可以基于不同的条件谓词在同一个条件队列上等待,使用notify单一的通知很容易导致类似于信号丢失的问题
  • 可以使用notify:同一条件谓词并且单进单出

使用notifyAll有时是低效的:唤醒的所有线程都需要竞争锁,并重新检验,而有时最终只有一个线程能执行

优化:条件通知

1 public synchronized void put(V v) throws InterruptedException
2 {
3     while(isFull())
4         wait();
5     boolean wasEmpty = isEmpty();
6     doPut(v);
7     if(wasEmpty)
8         notifyAll();
9 }

 5、示例:阀门类

 1 public class ThreadGate {
 2        private boolean isOpen;
 3        private int generation;
 4 
 5        public synchronized void close() {
 6               isOpen = false;
 7        }
 8 
 9        public synchronized void open() {
10               ++generation;
11               isOpen = true;
12               notifyAll();
13        }
14 
15        public synchronized void await() throws InterruptedException {
16               int arrivalGeneration = generation;
17               while (!isOpen && arrivalGeneration == generation)
18                      wait();
19        }
20 }
21 
22 可重新关闭的阀门

arrivalGeneration == generation为了保证在阀门打开时又立即关闭时,在打开时通知的线程都可以通过阀门

6、子类的安全问题

  • 如果在实施子类化时违背了条件通知或单词通知的某个需求,那么在子类中可以增加合适的通知机制来代表基类
  • 对于状态依赖的类,要么将其等待和通知等协议完全向子类公开(并且写入正式文档),要么完全阻止子类参与到等待和通知等过程中
  • 完全禁止子类化

7、封装条件队列

8、入口协议和出口协议

  • 入口协议:该操作的条件谓词
  • 出口协议:检查被该操作修改的所有状态变量,并确认它们是否使某个其他的条件谓词变为真,如果是,则通知相关的条件队列

 

三、显示的Condition对象

内置条件队列的缺点:每个内置锁都只能有一个相关联的条件队列,而多个线程可能在同一条件队列上等待不同的条件谓词,调用notifyAll通知的线程非等待同意谓词

Condition <-> Lock,内置条件队列 <-> 内置锁

  • Lock.newCondition()
  • 在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于时限的等待,以及公平的或非公平的队列操作
  • Condition对象继承了相关的Lock对象的公平性
  • 与wait、notify和notifyAll方法对应的分别是await、signal和signalAll
  • 将多个条件谓词分开并放到多个等待线程集,Condition使其更容易满足单次通知的需求(signal比signalAll更高效)
  • 锁、条件谓词和条件变量:件谓词中包含的变量必须由Lock来保护,并且在检查条件谓词以及调用await和signal时,必须持有Lock对象

 

 1 public class ConditionBoundedBuffer<T> {2     protected final Lock lock = new ReentrantLock();3     private final Condition notFull    = lock.newCondition();//条件:count < items.length4     private final Condition notEmpty  = lock.newCondition();//条件:count > 05     private final T[] items = (T[]) new Object[100];6     private int tail, head, count;7 8     public void put(T x) throws InterruptedException {9         lock.lock();
10         try {
11             while (count == items.length)
12                 notFull.await();//等到条件count < items.length满足
13             items[tail] = x;
14             if (++tail == items.length)
15                 tail = 0;
16             ++count;
17             notEmpty.signal();//通知读取等待线程
18         } finally {
19             lock.unlock();
20         }
21     }
22 
23     public T take() throws InterruptedException {
24         lock.lock();
25         try {
26             while (count == 0)
27                 notEmpty.await();//等到条件count > 0满足
28             T x = items[head];
29             items[head] = null;
30             if (++head == items.length)
31                 head = 0;
32             --count;
33             notFull.signal();//通知写入等待线程
34             return x;
35         } finally {
36             lock.unlock();
37         }
38     }
39 }

  

四、Synchronizer解析

  在ReentrantLock和Semaphore这两个接口之间存在许多共同点。两个类都可以用作一个”阀门“,即每次只允许一定数量的线程通过,并当线程到达阀门时,可以通过(在调用lock或acquire时成功返回),也可以等待(在调用lock或acquire时阻塞),还可以取消(在调用tryLock或tryAcquire时返回”假“,表示在指定的时间内锁是不可用的或者无法获取许可)。而且,这两个接口都支持中断不可中断的以及限时的获取操作,并且也都支持等待线程执行公平或非公平的队列操作。

原因:都实现了同一个基类AbstractQueuedSynchronizer(AQS)

 

 1 public class SemaphoreOnLock {//基于Lock的Semaphore实现
 2        private final Lock lock = new ReentrantLock();
 3        //条件:permits > 0
 4        private final Condition permitsAvailable = lock.newCondition();
 5        private int permits;//许可数
 6 
 7        SemaphoreOnLock(int initialPermits) {
 8               lock.lock();
 9               try {
10                      permits = initialPermits;
11               } finally {
12                      lock.unlock();
13               }
14        }
15 
16        //颁发许可,条件是:permits > 0
17        public void acquire() throws InterruptedException {
18               lock.lock();
19               try {
20                      while (permits <= 0)//如果没有许可,则等待
21                             permitsAvailable.await();
22                      --permits;//用一个少一个
23               } finally {
24                      lock.unlock();
25               }
26        }
27 
28        //归还许可
29        public void release() {
30               lock.lock();
31               try {
32                      ++permits;
33                      permitsAvailable.signal();
34               } finally {
35                      lock.unlock();
36               }
37        }
38 }
39 
40 使用Lock实现信号量

 

 1 public class LockOnSemaphore {//基于Semaphore的Lock实现
 2        //具有一个信号量的Semaphore就相当于Lock
 3        private final Semaphore s = new Semaphore(1);
 4 
 5        //获取锁
 6        public void lock() throws InterruptedException {
 7               s.acquire();
 8        }
 9 
10        //释放锁
11        public void unLock() {
12               s.release();
13        }
14 }
15 
16 使用信号量实现Lock

 

五、AbstractQueuedSynchronizer

最基本的操作:

  • 获取操作是一种依赖状态的操作,并且通常会阻塞(同步器判断当前状态是否允许获得操作,更新同步器的状态)
  • 释放并不是一个可阻塞的操作时,当执行“释放”操作时,所有在请求时被阻塞的线程都会开始执行

状态管理(一个整数状态):

  • 通过getState,setState以及compareAndSetState等protected类型方法来进行操作
  • 这个整数在不同子类表示任意状态。例:剩余的许可数量,任务状态
  • 子类可以添加额外状态

 

六、java.util.concurrent 同步器类中的AQS

1、ReentrantLock

  ReentrantLock只支持独占方式的获取操作,因此它实现了tryAcquire、tryRelease和isHeldExclusively

  ReentrantLock将同步状态用于保存锁获取操作的次数,或者正要释放锁的时候,才会修改这个变量

2、Semaphore与CountDownLatch

  Semaphore将AQS的同步状态用于保存当前可用许可的数量;CountDownLatch使用AQS的方式与Semaphore很相似,在同步状态中保存的是当前的计数值

3、FutureTask

  在FutureTask中,AQS同步状态被用来保存任务的状态

  FutureTask还维护一些额外的状态变量,用来保存计算结果或者抛出的异常

4、ReentrantReadWriteLock

  • 单个AQS子类将同时管理读取加锁和写入加锁
  • ReentrantReadWriteLock使用了一个16位的状态来表示写入锁的计数,并且使用了另一个16位的状态来表示读取锁的计数
  • 在读取锁上的操作将使用共享的获取方法与释放方法,在写入锁上的操作将使用独占的获取方法与释放方法
  • AQS在内部维护了一个等待线程队列,其中记录了某个线程请求的是独占访问还是共享访问:写操作独占获取;读操作可使第一个写之前的读都获取

转载于:https://www.cnblogs.com/linghu-java/p/9029011.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/389658.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于之前的函数式编程

之前写的函数式编程是我从 JavaScript ES6 函数式编程入门经典这本书里面整理的&#xff0c;然后只在第一篇里专门提到了&#xff0c;后面的话没有专门提到&#xff0c;而且引用了书中大量的文字&#xff0c;所以我把掘金这里的文章都删除了&#xff0c;然后在 CSDN 上面每一篇…

袋装决策树_袋装树是每个数据科学家需要的机器学习算法

袋装决策树袋装树木介绍 (Introduction to Bagged Trees) Without diving into the specifics just yet, it’s important that you have some foundation understanding of decision trees.尚未深入研究细节&#xff0c;对决策树有一定基础了解就很重要。 From the evaluatio…

[JS 分析] 天_眼_查 字体文件

0. 参考 js分析 猫_眼_电_影 字体文件 font-face 1. 分析 1.1 定位目标元素 1.2 查看网页源代码 1.3 requests 请求提取得到大量错误信息 对比猫_眼_电_影抓取到unicode编码&#xff0c;天_眼_查混合使用正常字体和自定义字体&#xff0c;难点在于如何从 红 转化为 美。 一开始…

深入学习Redis(4):哨兵

前言在 深入学习Redis&#xff08;3&#xff09;&#xff1a;主从复制 中曾提到&#xff0c;Redis主从复制的作用有数据热备、负载均衡、故障恢复等&#xff1b;但主从复制存在的一个问题是故障恢复无法自动化。本文将要介绍的哨兵&#xff0c;它基于Redis主从复制&#xff0c;…

1805. 字符串中不同整数的数目

1805. 字符串中不同整数的数目 给你一个字符串 word &#xff0c;该字符串由数字和小写英文字母组成。 请你用空格替换每个不是数字的字符。例如&#xff0c;“a123bc34d8ef34” 将会变成 " 123 34 8 34" 。注意&#xff0c;剩下的这些整数为&#xff08;相邻彼此至…

经天测绘测量工具包_公共土地测量系统

经天测绘测量工具包部分-乡镇第一师 (Sections — First Divisions of Townships) The PLSS Townships are typically divided into 36 Sections (nominally one mile on a side), but in the national standard this feature is called the first division because Townships …

洛谷 P4012 深海机器人问题【费用流】

题目链接&#xff1a;https://www.luogu.org/problemnew/show/P4012 洛谷 P4012 深海机器人问题 输入输出样例 输入样例#1&#xff1a; 1 1 2 2 1 2 3 4 5 6 7 2 8 10 9 3 2 0 0 2 2 2 输出样例#1&#xff1a; 42 说明 题解&#xff1a;建图方法如下&#xff1a; 对于矩阵中的每…

day5 模拟用户登录

_user "yangtuo" _passwd "123456"# passd_authentication False #flag 标志位for i in range(3): #for 语句后面可以跟else&#xff0c;但是不能跟elifusername input("Username:")password input("Password:")if username _use…

opencv实现对象跟踪_如何使用opencv跟踪对象的距离和角度

opencv实现对象跟踪介绍 (Introduction) Tracking the distance and angle of an object has many practical uses, especially in robotics. This tutorial explains how to get an accurate distance and angle measurement, even when the target is at a strong angle from…

spring cloud 入门系列七:基于Git存储的分布式配置中心--Spring Cloud Config

我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的&#xff0c;这次我们来看下spring cloud 团队自己创建的一个全新项目&#xff1a;Spring Cloud Config.它用来为分布式系统中的基础设施和微服务提供集中化的外部配置支持&#xff0c;分为服务端和客户端两个…

458. 可怜的小猪

458. 可怜的小猪 有 buckets 桶液体&#xff0c;其中 正好 有一桶含有毒药&#xff0c;其余装的都是水。它们从外观看起来都一样。为了弄清楚哪只水桶含有毒药&#xff0c;你可以喂一些猪喝&#xff0c;通过观察猪是否会死进行判断。不幸的是&#xff0c;你只有 minutesToTest…

熊猫数据集_大熊猫数据框的5个基本操作

熊猫数据集Tips and Tricks for Data Science数据科学技巧与窍门 Pandas is a powerful and easy-to-use software library written in the Python programming language, and is used for data manipulation and analysis.Pandas是使用Python编程语言编写的功能强大且易于使用…

图嵌入综述 (arxiv 1709.07604) 译文五、六、七

应用 图嵌入有益于各种图分析应用&#xff0c;因为向量表示可以在时间和空间上高效处理。 在本节中&#xff0c;我们将图嵌入的应用分类为节点相关&#xff0c;边相关和图相关。 节点相关应用 节点分类 节点分类是基于从标记节点习得的规则&#xff0c;为图中的每个节点分配类标…

聊聊自动化测试框架

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。之前学习自动化测试的过程中&#xff0c;一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上自己的一些实践&#xff0c;算是对“框架”有…

1971. Find if Path Exists in Graph

1971. Find if Path Exists in Graph 有一个具有 n个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 …

移动磁盘文件或目录损坏且无法读取资料如何找回

文件或目录损坏且无法读取说明这个盘的文件系统结构损坏了。在平时如果数据不重要&#xff0c;那么可以直接格式化就能用了。但是有的时候里面的数据很重要&#xff0c;那么就必须先恢复出数据再格式化。具体恢复方法可以看正文了解&#xff08;不格式化的恢复方法&#xff09;…

python 平滑时间序列_时间序列平滑以实现更好的聚类

python 平滑时间序列In time series analysis, the presence of dirty and messy data can alter our reasonings and conclusions. This is true, especially in this domain, because the temporal dependency plays a crucial role when dealing with temporal sequences.在…

基于SmartQQ协议的QQ自动回复机器人-1

0. 本项目的原始代码及我二次开发后的代码 1. 软件安装:【myeclipse6.0 maven2】 0. https://blog.csdn.net/zgmzyr/article/details/6886440 1. https://blog.csdn.net/shuzhe66/article/details/45009175 2. https://www.cnblogs.com/whgk/p/7112560.html<mirror><…

1725. 可以形成最大正方形的矩形数目

1725. 可以形成最大正方形的矩形数目 给你一个数组 rectangles &#xff0c;其中 rectangles[i] [li, wi] 表示第 i 个矩形的长度为 li 、宽度为 wi 。 如果存在 k 同时满足 k < li 和 k < wi &#xff0c;就可以将第 i 个矩形切成边长为 k 的正方形。例如&#xff0c…

帮助学生改善学习方法_学生应该如何花费时间改善自己的幸福

帮助学生改善学习方法There have been numerous studies looking into the relationship between sleep, exercise, leisure, studying and happiness. The results were often quite like how we expected, though there have been debates about the relationship between sl…