状态依赖性

定义:只有满足特定的状态才能继续执行某些操作(这些操作依赖于固定的状态,这些状态需要等待别的线程来满足)。

FutureTask,Semaphroe,BlockingQueue等,都是状态依赖性的类。

条件队列

条件对列:条件对列就是由于不满足继续的条件而被wait操作阻塞的线程队列。他们都在等待条件满足,然后被唤醒。

条件谓词:状态依赖性依赖的前提条件。如BlockingQueue中的isFull,isEmpty等。

条件等待中存在三个要素:加锁 + 条件谓词 + wait方法

wait方法和notify方法

我理解的wait方法:会释放锁+阻塞当前线程,放入条件对列,等待被唤醒,唤醒后,需要重新获得锁,获得锁之后继续执行wait那句代码所在的位置(即使wait在锁块的中间代码部分)。

notify(All)方法:只是唤醒条件队列中的线程。但是不释放锁。

使用wait notify方法的时候,一定要持有条件对列所属的锁。

使用轮询和休眠实现简单的状态依赖性阻塞

  1. while(true)
  2.         {
  3.             //这里不对循环上锁,不然这个锁就无法释放了,不对休眠上锁,休眠上锁,在休眠的时候别人也无法操作,永远都不可能有元素出去
  4.             synchronized (this)
  5.             {
  6.                 //如果队列不是满的,那么就放入元素
  7.                 if(!this.isFull())
  8.                 {
  9.                     this.doPut(v);
  10.                     return;
  11.                 }
  12.             }
  13.             //否则休眠,退出cpu占用
  14.             Thread.sleep(SLEEP_GRANULARITY);
  15.         }
  16.     }

使用条件队列来实现状态依赖性阻塞

  1. public synchronized void put(V v) throws InterruptedException
  2.    {
  3.        while(this.isFull())
  4.        {
  5.            //这里挂起程序,会释放锁
  6.            this.wait();
  7.        }
  8.        //如果队列不为满的,那么程序被唤醒之后从新获取锁
  9.        this.doPut(v);
  10.        //执行结束,唤醒其他队列
  11.        this.notifyAll();
  12.    }

注意上面要使用while。

对于监视器来说,wait操作产生的线程,都放在这个监视器唯一的条件队列里。

如果使用Lock,可以使用condition来产生不同的条件对列。

注意上面的 this.notifyAll();代码,将会唤醒这个监视器条件队列里所有等待的线程。其实这里只用唤醒因为empty阻塞的线程,而不用唤醒因为full阻塞的线程。

如果使用 this.notify(),只会随机唤醒一个,如果唤醒的是因为full堵塞的线程,那么就可能没有正常唤醒。影响性能,甚至造成活跃性的危险。

这种情况下,可以使用Lock和Condition来改造。

  1. protected final Lock lock = new ReentrantLock();
  2. private final Condition notFull = lock.newCondition();
  3. private final Condition notEmpty = lock.newCondition();
  4.  
  5.  public void put(T x) throws InterruptedException {
  6.         lock.lock();
  7.         try {
  8.             while (count == items.length)
  9.                 notFull.await();
  10.             items[tail] = x;
  11.             if (++tail == items.length)
  12.                 tail = 0;
  13.             ++count;
  14.             notEmpty.signal();
  15.         } finally {
  16.             lock.unlock();
  17.         }
  18.     }

注意:这里不是signalAll。

阀门类

使用闭锁CountDownLatch,传入1的时候可以作为阀门开关。前提是在其他线程的第一步先执行开关的await。使用开关的countDown方法就可以打开开关。

但是这种阀门,只能打开,不能关闭。

使用wait和notifyAll来实现可重新关闭的阀门。

Condition

注意,由于Condition对象继承自Object,它也有wait,notify,notifyAll方法,其实它对应方法名字应该是await,signal,signalAll。

Java并发编程实战 第14章 构建自定义的同步工具的更多相关文章

  1. Java并发编程实战 第5章 构建基础模块

    同步容器类 Vector和HashTable和Collections.synchronizedXXX 都是使用监视器模式实现的. 暂且不考虑性能问题,使用同步容器类要注意: 只能保证单个操作的同步. ...

  2. Java并发编程实战---第六章:任务执行

    废话开篇 今天开始学习Java并发编程实战,很多大牛都推荐,所以为了能在并发编程的道路上留下点书本上的知识,所以也就有了这篇博文.今天主要学习的是任务执行章节,主要讲了任务执行定义.Executor. ...

  3. Java并发编程实战 第16章 Java内存模型

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

  4. 【java并发编程实战】第一章笔记

    1.线程安全的定义 当多个线程访问某个类时,不管允许环境采用何种调度方式或者这些线程如何交替执行,这个类都能表现出正确的行为 如果一个类既不包含任何域,也不包含任何对其他类中域的引用.则它一定是无状态 ...

  5. Java并发编程实战 第8章 线程池的使用

    合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...

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

    一.状态依赖性管理 对于单线程程序,某个条件为假,那么这个条件将永远无法成真 在并发程序中,基于状态的条件可能会由于其他线程的操作而改变 可阻塞的状态依赖操作的结构 acquire lock on o ...

  7. Java并发编程实战 第3章 对象的共享

    可见性 可见性是由于java对于多线程处理的内存模型导致的.这似乎是一种失败的设计,但是JVM却能充分的利用多核处理器的强大性能,例如在缺乏同步的情况下,Java内存模型允许编译器对操作顺序进行重排序 ...

  8. JAVA并发编程实战---第三章:对象的共享(2)

    线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线 ...

  9. 那些年读过的书《Java并发编程实战》一、构建线程安全类和并发应用程序的基础

    1.线程安全的本质和线程安全的定义 (1)线程安全的本质 并发环境中,当多个线程同时操作对象状态时,如果没有统一的状态访问同步或者协同机制,不同的线程调度方式和不同的线程执行次序就会产生不同的不正确的 ...

随机推荐

  1. idea 编译 netty 源码

    git clone netty 源码,运行 example 报错 全量 mvn compile -DskipTests=true 后,依然报错 手动在 netty-buffer 模块中添加对应的依赖 ...

  2. Prometheus在Kubernetes下的服务发现机制

    Prometheus作为容器监控领域的事实标准,随着以Kubernetes为核心的云原生热潮的兴起,已经得到了广泛的应用部署.灵活的服务发现机制是Prometheus和Kubernetes两者得以连接 ...

  3. HashMap根据value值排序

    )))); Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); for (Map.Entry s : entr ...

  4. mysql——单表查询——聚合函数——概念

    使用聚合函数查询 group by关键字通常和聚合函数一起使用 .count()函数 count()函数用来统计记录的条数 举例:使用count()函数统计employee表的记录数 select c ...

  5. json得回顾

  6. Redis配置主从时报错“Could not connect to Redis at 192.168.0.50:6379: Connection refused not connected>”

    配置Redis主从时,修改完从节点配置文件,然后报错 [root@Rich七哥-0-50 redis]# /opt/redis/redis-cli -h 192.168.0.50 Could not ...

  7. LayaAir疑难杂症之四:laya引擎自动断点到bundle.js文件中且无报错,但程序不再执行

    在一次断点调试中,突然程序不再按照博主指定的断点执行,莫名其妙端点到了bundle.js文件中的某一行中,这是不应该的,第一次时间反应就是引擎出了问题,但是总不能让博主卸载重装吧. 经过查找资料,询问 ...

  8. [转帖]Linux下批量替换文件内容方法

    Linux下批量替换文件内容方法 https://www.cnblogs.com/fjping0606/p/4428850.html 刚才用到的命令 原作者写的挺好的记录一下 以后 用. 1:查找fi ...

  9. python nonlocal 的具体原理

    很多文章都大概列了下nonlocal的具体用法,比如看到几篇文章写的 “nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量” 看完以后我感觉自己是懂了,但光从这句话来说还没完全理解它 ...

  10. springboot - 应用实践(N)使用springboot内置的@Scheduled

    1.springboot开箱即用,内置调度任务的使用. 建一个简单的springboot工程,pom.xml: <?xml version="1.0" encoding=&q ...