• 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方法同样会间接地执行迭代操作,当容器所谓另一个容器的元素或者键值时就会出现这种情况。

  • 相关阅读:
    下载及爬取网页内容
    对于for循环的理解
    记录安装fiddle出现的问题
    Django
    12种可以参考的思路关于代码能干什么
    “字符文本中字符太多”错误及解决方法
    jQuery参考:jquery中的$(document).ready()与window.onload的区别
    页面定时刷新功能实现
    HTML:关于位置的几个概念
    Lambda表达式
  • 原文地址:https://www.cnblogs.com/tuhooo/p/8073718.html
Copyright © 2020-2023  润新知