这一节来讲一讲java.util.concurrent这个包里的一些重要的线程安全有关类。

synchronized容器

synchronized容器就是把自己的内部状态封装起来,通过把每一个public方法设置成同步来控制对共享变量的访问的容器。主要包括Vector, Hashtable,以及Collections.synchronizedxxx()方法提供的wrapper。

synchronized容器的问题-client locking

首先,synchronzied容器虽然是线程安全的,但是要访问容器内部数据的线程只能先拿到容器的内置锁才能访问,实际上相当于串行访问,CPU利用率和效率都不高。
另外还有一个值得注意的地方,就是用户代码使用synchronized容器时,如果需要做一些复合操作,比如put-if-absent,仍然要显式加锁(称为client locking),否则会产生race condition。
比如以下操作:

 public Object getLast(Vector list){
  int last = list.size() - 1; //
  return list.get(last); //
}
public void removeLast(Vector list){
  int last = list.size() - 1; //
  list.remove(last); //
}

以上两个方法都对Vector进行了复合操作,在不加锁的情况下可能产生这样一种场景:线程A调用getLast(),同时线程B调用removeLast()。线程A进行step 1拿到last同时线程B也拿到同样的last;此时由于线程调度上的原因,线程B先执行了step 4删除了最后一个节点,而线程A在此之后才执行step 2, 由于最后一个节点已被删除,线程A这里会报ArrayIndexOutOfBoundsException,而这个错误并不是用户希望看到的。

  

所以如果要按照类似的方法使用synchronized容器的话还是需要自己加锁。由于这些容器内部的线程安全策略是使用自己的内置锁,所以用户代码加锁的时候需要用到的是容器本身。

 public Object getLast(Vector list){
  synchronized(list){
    int last = list.size() - 1; //
    return list.get(last); //
  }
}
public void removeLast(Vector list){
  synchronized(list){
    int last = list.size() - 1; //
    list.remove(last); //
  }
}

除了这些用户自定义的复合操作之外,其实iteration也算复合操作,所以也应该加锁。此处应注意两点:

  1. 容器自带的Iterator本身不支持并发修改,所以它提供了一个所谓的fail-fast的并发修改报错机制,即容器自身维护一个modCount域,Iterator在创建时记录这个modCount的值,如果在用户遍历容器的过程中modCount值发生了改变,则说明有另一个线程对容器做出了修改,那么Iterator马上会抛出ConcurrentModificationException。

    这个机制严格意义上并不能够100%地探测到并发修改,因为modCount这个域并不是volatile的,在判断    

    if(modCount == expectedModCount)

   时也并未加锁。作者描述这个机制是在考虑性能的情况下所做的一个best-effort的努力。总之,不应该对这个机制做过多的依赖。

       2.  有一些容器自带的方法看起来很无辜,但内部会用到iterator,所以用户用到这些无辜方法的时候还是要加锁。比如我们常用的toString, for-each语法,hashCode, equals, containsAll, removeAll, retainAll, 以其他容器为参数的构造器,等等。而这些方法有时候也是被隐式调用的,很难检查到,比如:

    1 //...add some elements to the set
     System.out.println("DEBUG: added ten elements to " + set);

    这里打印时set.toString()方法被隐式调用了。

client locking的问题

由于client code尝试使用容器内部的线程安全机制,所以容易导致starvation和deadlock,这是因为任意代码都可以使用容器的内置锁,散落在各处的线程安全机制使得程序很难维护和debug。如果要解决这个问题,可以把容器克隆到线程内部进行使用,但每次使用的时候都要重新克隆,要考虑克隆本身带来的代价。

Concurrent容器

相比于synchronized容器,Concurrent容器可以提供更高的并发性。
如果需要并发的Map,相比于synchronized Map,可以优先考虑ConcurrentHashMap;同理,相比于synchronized List/Set,可以优先考虑CopyOnWriteArrayList/Set;相比于synchronized SortedMap/Set,可以优先考虑ConcurrentSkipMap/Set。

ConcurrentHashMap

+ 使用了比Hashtable更细粒度的lock striping线程安全策略,支持多个(有限个)线程同时读写。
+ 提供的Iterator是weakly consistent的,容许并发修改。
- size/isEmpty等方法只提供估算值。
- 由于使用的锁对象是private的,不支持client-side locking。(但是提供put-if-absent等复合操作)

CopyOnWriteArrayList

+ 每次改动时创建和发布新的collection copy。
+ 内部array是effectively immutable的,因此发布后可以不加锁地安全访问。
+ 适用于iteration >> modification的情况,如listeners。

BlockingQueue与生产者-消费者

BlockingQueue的最大好处是它不仅是一个简单的容器,它还能提供flow-control,能让程序在消息过多的情况下仍然保持健壮。

特殊的BlockingQueue: SynchronousQueue

一种很特殊的queue,实际上没有内在的存储,只是用于线程间的交接(rendezvous)。适用于消费者够多的情况,比起BlockingQueue的最大好处是没有交接成本。

 Thread producer = new Thread("PRODUCER") {
  public void run() {
    String event = "MY_EVENT";
    try {
      queue.put(event); // thread will block here
      System.out.printf("[%s] published event : %s %n", Thread.currentThread().getName(), event);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
};
producer.start(); // starting publisher thread Thread consumer = new Thread("CONSUMER") {
  public void run() {
    try {
      String event = queue.take(); // thread will block here
      System.out.printf("[%s] consumed event : %s %n", Thread.currentThread().getName(), event);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
};
consumer.start(); // starting consumer thread [PRODUCER] published event : MY_EVENT
[CONSUMER] consumed event : MY_EVENT

Synchronizers

所谓的synchronizer,就是能够根据其内部状态调节线程的control flow的对象。

CountDownLatch

主要方法:
  - countDown
  - await
CountDownLatch有如一个阀门,在其达到最终状态前阀门关闭,线程不可通过。达到最终状态时,阀门打开,所有线程通过。打开后的阀门永远打开,状态不再改变。

适用情景:

  • 等待所依赖的资源全部加载完成后才继续。 
  • 初始化顺序中各个service之间的相互等待。
  • 等待所有参与的player都准备好才开始游戏。

FutureTask

主要方法:get
task真正结束前get方法会阻塞,直到task执行结束/被取消/抛异常。

Semaphore

主要方法:
  - release
  - acquire

有有限多个permit,acquire时如果permit为0会阻塞,但release可以执行无限多次。

适合:控制可以同时访问某资源的activity数量。可用来实现资源池或将容器设为可以存储有限个元素的容器。

CyclicBarrier

主要方法:await

必须所有线程到达Barrier时,所有线程才能通过。

Latch用来等待事件;Barrier用来等待其它线程。

适用场景:N等N

 public class CellularAutomata {
private final Board mainBoard;
private final CyclicBarrier barrier;
private final Worker[] workers; public CellularAutomata(Board board) {
this.mainBoard = board;
int count = Runtime.getRuntime().availableProcessors();
this.barrier = new CyclicBarrier(count,
new Runnable() {
public void run() {
mainBoard.commitNewValues();
}});
this.workers = new Worker[count];
for (int i = 0; i < count; i++)
workers[i] = new Worker(mainBoard.getSubBoard(count, i));
} private class Worker implements Runnable {
private final Board board; public Worker(Board board) { this.board = board; }
public void run() {
while (!board.hasConverged()) {
for (int x = 0; x < board.getMaxX(); x++)
for (int y = 0; y < board.getMaxY(); y++)
board.setNewValue(x, y, computeValue(x, y));
try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
} private int computeValue(int x, int y) {
// Compute the new value that goes in (x,y)
return 0;
}
} public void start() {
for (int i = 0; i < workers.length; i++)
new Thread(workers[i]).start();
mainBoard.waitForConvergence();
}
}

[JCIP笔记](五)JDK并发包的更多相关文章

  1. Java 并发编程实践基础 读书笔记: 第三章 使用 JDK 并发包构建程序

    一,JDK并发包实际上就是指java.util.concurrent包里面的那些类和接口等 主要分为以下几类: 1,原子量:2,并发集合:3,同步器:4,可重入锁:5,线程池 二,原子量 原子变量主要 ...

  2. Java并发程序设计(四)JDK并发包之同步控制

    JDK并发包之同步控制 一.重入锁 重入锁使用java.util.concurrent.locks.ReentrantLock来实现.示例代码如下: public class TryReentrant ...

  3. C#可扩展编程之MEF学习笔记(五):MEF高级进阶

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  4. 《MFC游戏开发》笔记五 定时器和简单动画

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9332377 作者:七十一雾央 新浪微博:http:// ...

  5. (转)Qt Model/View 学习笔记 (五)——View 类

    Qt Model/View 学习笔记 (五) View 类 概念 在model/view架构中,view从model中获得数据项然后显示给用户.数据显示的方式不必与model提供的表示方式相同,可以与 ...

  6. java之jvm学习笔记五(实践写自己的类装载器)

    java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...

  7. Crazyflie笔记五: CRTP 实时通信协议(一)(转)

    源:Crazyflie笔记五: CRTP 实时通信协议(一) 这里详细介绍了 Crazyflie 的 CRTP实时通信协议的相关内容,由于内容很长,分几篇博文来讲述.这里是第一节内容.欢迎交流:301 ...

  8. Learning ROS for Robotics Programming Second Edition学习笔记(五) indigo computer vision

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  9. Typescript 学习笔记五:类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  10. Django开发笔记五

    Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.页面继承 定义base.html: <!DOC ...

随机推荐

  1. Python基础题

    1. 执行Python脚本的两种方式: Chmod +x 脚本 ./脚本(路径的方式) Python 脚本 2. 简述位.字节的关系 一个字节=8位 3. 简述ASCII.unicode.utf-8/ ...

  2. SpringBoot中@ManyToMany的坑

    我在User表中添加了manytomany的外键映射 @ManyToMany(fetch=FetchType.EAGER) @JoinTable(name="user_role", ...

  3. [转]pycharm常用快捷键及设置

    PyCharm3.0默认快捷键(翻译的) PyCharm Default Keymap 1.编辑(Editing) Ctrl + Space    基本的代码完成(类.方法.属性)Ctrl + Alt ...

  4. JavaScript实现轮播图(隔3秒显示一张图)

    <!DOCTYPE html><html> <head> <script> var time; function init(){ //获取div里面的东 ...

  5. 瞎扯设计模式1:单例模式 饿汉模式 懒汉模式 线程安全的单例 singleton 设计模式 java

    [原创声明]此文为本人原创,欢迎转载,转载请注明出处,作者链接~ http://www.cnblogs.com/m-yb/p/8833085.html 单例很常用,面试也经常被问,如:不用自定义锁怎么 ...

  6. 【转载】C++基本功和 Design Pattern系列 ctor & dtor

    最近实在是太忙了,无工夫写呀.只能慢慢来了.呵呵,今天Aear讲的是class.ctor 也就是constructor, 和  class.dtor, destructor. 相信大家都知道const ...

  7. vue基础特性

    在这里我们主要是讲解一些vue实例的属性和一些基础的指令 vue实例属性: 其实和我们之前所学的对象的属性是相似的东西 vue的基础指令: 对于指令,大家可能之前么有接触过相关的概念,其实大家可以这样 ...

  8. 存储过程学习笔记(SQL数据库

    一.   存储过程简介 Sql Server的存储过程是一个被命名的存储在服务器上的Transacation-Sql语句集合,是封装重复性工作的一种方法,它支持用户声明的变量.条件执行和其他强大的编程 ...

  9. 原生js代码挑战之动态添加双色球

    var ballArr = []; //存放已有的红球,用来排除重复和排序window.onload = function(){ var btn = document.createElement(&q ...

  10. Kylin系列之二:原理介绍

    Kylin系列之二:原理介绍 2018年4月15日 15:52 因何而生 Kylin和hive的区别 1. hive主要是离线分析平台,适用于已经有成熟的报表体系,每天只要定时运行即可. 2. Kyl ...