转自:java并发编程实战

5.3阻塞队列和生产者-消费者模式

BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法。如果队列已经满了,那么put方法将阻塞直到空间可用;如果队列为空,那么take方法将阻塞直到有元素可用。队列可以是有界的也可以是无界的。

如果生产者生成工作的速率比消费者处理工作的速率款,那么工作项会在队列中累计起来,最终好紧内存。同样,put方法的阻塞特性也极大地简化了生产者的编码。如果使用有界队列,当队列充满时,生产者将阻塞并不能继续生产工作,而消费者就有时间来赶上工作的进度。阻塞队列同样提供了一个offer方法,如果数据项不能被添加到队列中,那么将返回一个失败的状态。这样你就能创建更多灵活的策略来处理负荷过载的情况。

在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具:他们能一直并防止产生过多的工作项,使应用程序在负荷过载的情况下边的更加健壮。

/**
* java并发编程实战
* 5.3.1桌面搜索
* 爬虫查找所有文件并放入队列
* Created by mrf on 2016/3/7.
*/
public class FileCrawler implements Runnable {
private final BlockingQueue<File> fileQueue;
private final FileFilter fileFilter;
private final File root; public FileCrawler(BlockingQueue<File> fileQueue, FileFilter fileFilter, File root) {
this.fileQueue = fileQueue;
this.fileFilter = fileFilter;
this.root = root;
} @Override
public void run() {
try {
crawl(root);
} catch (InterruptedException e) {
//恢复中断
Thread.currentThread().interrupt();
e.printStackTrace();
}
} private void crawl(File root) throws InterruptedException {
File[] entries = root.listFiles(fileFilter);
if (entries!=null){
for (File entry : entries) {
if (entry.isDirectory()){
crawl(entry);
}else if (!alreadyIndexed(entry)){
fileQueue.put(entry);
}
}
}
} private boolean alreadyIndexed(File entry){
//检查是否加入索引
return false;
}
} /**
* 消费者
* 将爬虫结果队列取出并加入索引
*/
class Indexer implements Runnable{
private static final int BOUND = 100;
private static final int N_CONSUMERS = 2;
private final BlockingQueue<File> queue; Indexer(BlockingQueue<File> queue) {
this.queue = queue;
} @Override
public void run() {
try {
while (true){
indexFile(queue.take());
}
}catch (InterruptedException e){
Thread.currentThread().interrupt();
}
} private void indexFile(File take) {
//将文件加入索引
} public static void startIndexing(File[] roots){
BlockingQueue<File> queue = new LinkedBlockingDeque<>(BOUND);
FileFilter fileFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return true;
}
};
for (File root:roots) {
new Thread(new FileCrawler(queue,fileFilter,root)).start();
}
for (int i = 0; i < N_CONSUMERS; i++) {
new Thread(new Indexer(queue)).start();
}
}
}

  5.5信号量

Semaphore中管理着一组虚拟的许可(permit)。许可的初始数量可通过构造函数来指定。在执行操作时可以首先获得许可(只要还有剩余的许可),并在使用以后释放许可。如果没有许可,那么acquire将阻塞直到有许可(或者被中断或者操作超时)。release方法将返回一个许可给信号量。计算信号量的一种简化形式是二值信号量,即初始值为1的Semaphore。二值信号量可以用作互斥体(mutex),并具备不可重入的加锁语义:谁拥有这个唯一的许可,谁就拥有了互斥锁。

/**
* java 并发编程实战
* 5-14使用Semaphore做容器设置边界
* 信号量
* Created by mrf on 2016/3/8.
*/
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem; // public BoundedHashSet(Set<T> set, Semaphore sem) {
// this.set = set;
// this.sem = sem;
// } public BoundedHashSet(int bound){
this.set = Collections.synchronizedSet(new HashSet<T>());
sem = new Semaphore(bound);
} public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
}finally {
if (!wasAdded){
sem.release();
}
}
}
public boolean remove(Object o){
boolean wasRemoved = set.remove(o);
if (wasRemoved){
sem.release();
}
return wasRemoved;
}
}

  5.6构建高效且可伸缩的结果缓存

/**
* java并发编程实战
* 5-16使用HashMap和不同机制来初始化缓存
* 实现将曾经计算过的命令缓存起来,方便相同的计算直接出结果而不用重复计算
* Created by mrf on 2016/3/8.
*/
public interface Computable<A,V> {
V compute(A arg) throws InterruptedException;
} class ExpensiveFunction implements Computable<String,BigInteger>{ @Override
public BigInteger compute(String arg) throws InterruptedException {
//在经过长时间的计算后
return new BigInteger(arg);
}
} /**
* 保守上锁办法
* 每次只有一个线程能执行compute,性能差
* @param <A>
* @param <V>
*/
class Memoizer1<A,V> implements Computable<A,V>{
@GuardedBy("this")
private final Map<A,V> cache = new HashMap<>();
private final Computable<A,V> c; public Memoizer1(Computable<A, V> c) {
this.c = c;
} @Override
public synchronized V compute(A arg) throws InterruptedException {
V result = cache.get(arg);
if (result==null){
result = c.compute(arg);
cache.put(arg,result);
}
return result;
}
} /**
* 5-17
* 改用ConcurrentHashMap增强并发性
* 但还有个问题,就是只有计算完的结果才能缓存,正在计算的没有缓存,
* 这将导致一个长时间的计算没有放入缓存,另一个又开始重复计算。
* @param <A>
* @param <V>
*/
class Memoizer2<A,V> implements Computable<A,V>{ private final Map<A,V> cache = new ConcurrentHashMap<>();
private final Computable<A,V> c; Memoizer2(Computable<A, V> c) {
this.c = c;
} @Override
public V compute(A arg) throws InterruptedException {
V result = cache.get(arg);
if (result ==null){
result = c.compute(arg);
cache.put(arg,result);
}
return result;
}
} /**
* 几乎完美:非常好的并发性,缓存正在计算中的结果
* 但compute模块中if代码块是非原子性的,这样可能导致两个相同的计算
* @param <A>
* @param <V>
*/
class Memoizer3<A,V> implements Computable<A,V>{
private final Map<A,Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A,V> c; Memoizer3(Computable<A, V> c) {
this.c = c;
} @Override
public V compute(final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f==null){
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = ft;
cache.put(arg,ft);
ft.run();
}
try {
return f.get();
} catch (ExecutionException e) {
//抛出正在计算
e.printStackTrace();
}
return null;
}
} /**
* 使用ConcurrentHashMap的putIfAbsent解决原子问题
* 若计算取消则移除
* @param <A>
* @param <V>
*/
class Memoizer<A,V> implements Computable<A,V>{
private final ConcurrentHashMap<A,Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A,V> c; Memoizer(Computable<A, V> c) {
this.c = c;
} @Override
public V compute(final A arg) throws InterruptedException {
while (true){
Future<V> f = cache.get(arg);
if (f==null){
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg,ft);
if (f==null){
f = ft;ft.run();
}
}
try {
return f.get();
} catch (CancellationException e){
cache.remove(arg,f);
} catch(ExecutionException e) {
//抛出正在计算
e.printStackTrace();
}
return null;
} }
}

  小结:

  • 可变状态是直观重要的(It's the mutable state,stupid)。所有的并发问题都可以归结为如何协调对并发状态的访问。可变状态越少,就越容易确保线程的安全性。
  • 尽量将域声明为final类型,除非需要他们是可变的。
  • 不可变对象一定是线程安全的。不可变对象能极大地降低并发编程的复杂性。他们更为简单而且可以任意共享而无须使用加锁或保护性复制等机制。
  • 封装有助于管理复杂性。在编写线程安全的程序时,虽然可以将所有数据都保存在全局变量中,但为什么要这样做?将数据封装在对象中,更易于维持不变性条件:将同步机制封装在对象中,更易于遵循同步策略。
  • 用锁来保护每个可变变量。
  • 当保护同一个不变性条件中的所有变量时,要使用同一个锁。
  • 在执行复合操作期间,要持有锁。
  • 如果从多个线程中访问同一个可变变量时没有同步机制,那么程序会出现问题。
  • 不要故作聪明地腿短出不需要使用同步。
  • 在设计过程中考虑线程安全,或者在文档中明确地指出他不是线程安全的。
  • 将同步策略文档化。

  

java并发编程实战学习(3)--基础构建模块的更多相关文章

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

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

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

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

  3. 【JAVA并发编程实战】5、构建高效且可伸缩的结果缓存

    首先创建一个借口,用来表示耗费资源的计算 package cn.xf.cp.ch05; public interface Computable<A, V> { V compute(A ar ...

  4. Java并发编程实战 第14章 构建自定义的同步工具

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

  5. 【java并发编程实战】-----线程基本概念

    学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...

  6. 《Java并发编程实战》/童云兰译【PDF】下载

    <Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Jav ...

  7. 《java并发编程实战》笔记

    <java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为:  Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...

  8. Java并发编程实战——读后感

    未完待续. 阅读帮助 本文运用<如何阅读一本书>的学习方法进行学习. P15 表示对于书的第15页. Java并发编程实战简称为并发书或者该书之类的. 熟能生巧,不断地去理解,就像欣赏一部 ...

  9. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

随机推荐

  1. HTTP03--DNS知识

    一.域名解析过程 1. 浏览器检查自身缓存,缓存时间为几分钟到小时不等,通过设置TTL属性确定. 2. 若1未找到,则查OS缓存,如windows下的host文件,linux的/etc/hosts文件 ...

  2. 用Razor做静态页面生成器

    本来是用asp.net webpages做的博客网站,数据库用了一个陌生的本地数据库,只是觉得用起来很爽快,用新鲜的东西有一种刺激.后来数据库挂了,估计是存某个字段的时候出了问题,可是新鲜的东西,也不 ...

  3. 我的ORM之二--添加

    我的ORM索引 添加的语法: var 影响行数 = dbr.表.Insert(实体).Execute(); 实体类型 1. 任何C#类. 如:public class Entity{   public ...

  4. 如何让Hadoop读取以gz结尾的文本格式的文件

    背景: 搜索引擎在build全量时,会产生数G的xml的中间文件,我需要去查询这些中间文件中,是否有某个特殊的字符.xml文件有很多,每个都有几百M,存储在hdfs上,而且是以gz结尾的文本格式的文件 ...

  5. jQuery同步Ajax带来的UI线程阻塞问题及解决办法

    俗话说不作死就不会死,今天作死了一回,写了一个比较二逼的函数,遇到了同步Ajax引起的UI线程阻塞问题,在此记录一下. 事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的原则 ...

  6. 【面试必备】javascript操作DOM元素

    前言 时间过的真快,不知不觉就到年底了.问问自己,这一年你对自己的工作满意吗? 评价标准是什么呢?当然是马云的那两条准则了:钱给到了吗?干的爽吗?如果答案都是no,那么,你准备好跳槽了吗? 为了应对年 ...

  7. 解决在onCreate()过程中获取View的width和Height为0的4种方法

    很经常当我们动态创建某些View时,需要通过获取他们的width和height来确定别的view的布局,但是在onCreate()获取view的width和height会得到0.view.getWid ...

  8. 如何为编程爱好者设计一款好玩的智能硬件(三)——该选什么样的MCU呢?

    一.我的构想:如何为编程爱好者设计一款好玩的智能硬件(一)——即插即用.积木化.功能重组的智能硬件模块构想 二.别人家的孩子:如何为编程爱好者设计一款好玩的智能硬件(二)——别人是如何设计硬件积木的! ...

  9. C# 版 flvmerge:快速合并多个flv文件

    网上的视频很多都是分片的flv文件,怎么把他们合为一体呢?GUI工具就不考虑了,不适合批量执行,不适合在后台运行.有没有命令行工具或库可以实现呢? ffmpeg 提供了一个方法: (1)先把flv文件 ...

  10. IOS Socket 04-利用框架CocoaAsyncSocket实现客户端/服务器端

    这篇文章,我们介绍CocoaAsyncSocket框架的使用,主要介绍实现客户端/服务器端代码,相信在网上已经很多这样的文章了,这里做一下自己的总结.这里介绍使用GCD方式 一.客户端 1.下载地址 ...