zoukankan      html  css  js  c++  java
  • Java并发编程(十三)同步容器类

    同步容器类

    Vector、HashTable,我用的很少;Vecotr的实现和ArrayList挺接近的,不同的是Vector中很多的方法都用synchronized进行了同步。在不强调线程安全地时候用ArrayList,在需要线程安全地时候用Vector。
    实现线程安全的方法:把它们的状态封装起来,并对每个公有方法都进行同步,使得每次都只有一个线程能访问容器的状态

    同步容器类问题

    在某些情况下需要额外的客户端加锁来保护复合操作:

    1. 迭代,遍历容器中所有元素
    2. 跳转,根据当前顺序找到一下个元素
    3. 条件运算,若没有则添加

    执行"先检查再运行"的demo

     1 public class VectorDemo {
     2     // 获取最后一个元素
     3     public static Object getLast(Vector list) {
     4         int lastIndex = list.size() - 1;
     5         return list.get(lastIndex);
     6     }
     7     // 删除最后一个元素
     8     public static void deleteLast(Vector list) {
     9         int lastIndex = list.size() - 1;
    10         list.remove(lastIndex);
    11     }
    12 }

    在客户端中给Vector加上锁,获得一个线程安全的版本:

     1 public class VectorDemo {
     2     // 获取最后一个元素
     3     public static Object getLast(Vector list) {
     4         synchronized (list) {
     5             int lastIndex = list.size() - 1;
     6             return list.get(lastIndex);
     7         }
     8     }
     9     // 删除最后一个元素
    10     public static void deleteLast(Vector list) {
    11         synchronized (list) {
    12             int lastIndex = list.size() - 1;
    13             list.remove(lastIndex);
    14         }
    15     }
    16 }

    支持客户端加锁,可以创建一些新的操作,只要知道应该使用哪一个锁,那么这些新的操作就与容器的其他操作一样都是原子操作。在这个例子中getLast和deleteLast与Vector中其他的方法都共享一把锁,也就是Vector实例自己,同一时刻只能进入其中的一个synchronized代码块中。

    在同步容器类中,这些复合操作在没有客户端加锁的情况下仍然是线程安全的,在其他线程并发修改容器的时候,可能会有意料之外的行为。

    两个方法都是线程安全的,但是组合起来会导致异常。可以在客户端加锁来解决不可靠迭代的问题,但是要牺牲一些伸缩性。通过在迭代期间持有Vector的锁可以防止其他线程在迭代期间修改Vector。

    迭代器与ConcurrentModificationException

    对容器进行迭代的标准方式都是用Iterator。在同步容器类中,进行迭代时并没有考虑到并发修改,而是用的"及时失败",终于搞懂了快速失效了。这意味着在迭代时容器被修改将会抛出一个ConcurrentModificationException异常。

    这种及时失败的迭代器并不是一种完备的处理机制,而只是善意地捕获并发错误,因此只能作为并发问题的预警指示器。采用的方法是,将计数器的变化与容器关联起来:如果在迭代期间计数器被修改,那么hasNext或next将抛出ConcurrentModification。然而这种检查是在没有同步的情况下进行的,因此可能会看到失效值,而迭代可能并没有意识到已经发生了修改。

    为什么不希望在迭代的时候加锁:

    1. 容器的规模很大,或者在每个元素上执行的操作时间很长,那么这些线程将长时间等待。
    2. 一直持有一个锁,可能产生死锁,降低程序的可伸缩性,持有锁的时间越长,锁上的竞争越激烈,如果许多线程都等待着锁被释放,那么将极大地降低吞吐量和CPU的利用率。

    隐藏迭代器

    加锁可以防止迭代器抛出ConcurrentModificationException,但是要记住在所有对共享容器进行迭代的地方都需要加锁。

    如果状态与保护它的同步代码之间相隔越远,那么开发人员就越容易忘记在访问状态的时候使用正确的同步。正如封装对象的状态有助于维持不变性的条件一样,封装对象的同步机制同样有助于确保实施同步策略。

    容器的hashCode和equals方法同样会间接地执行迭代操作,当容器所谓另一个容器的元素或者键值时就会出现这种情况。

  • 相关阅读:
    一行代码搞定Dubbo接口调用
    测试周期内测试进度报告规范
    jq 一个强悍的json格式化查看工具
    浅析Docker容器的应用场景
    HDU 4432 Sum of divisors (水题,进制转换)
    HDU 4431 Mahjong (DFS,暴力枚举,剪枝)
    CodeForces 589B Layer Cake (暴力)
    CodeForces 589J Cleaner Robot (DFS,或BFS)
    CodeForces 589I Lottery (暴力,水题)
    CodeForces 589D Boulevard (数学,相遇)
  • 原文地址:https://www.cnblogs.com/tuhooo/p/8073718.html
Copyright © 2011-2022 走看看