基础
Java
Java
  • 基础知识
    • Java 语言的特点
    • Java 基础
      • 语法基础
      • 类型
      • 泛型
      • 注解
      • 异常
      • 反射机制
      • Java 容器
    • Java IO
      • 基础IO
      • NIO
    • Java 并发
      • Java 内存模型
        • 主内存与工作内存
        • 对于 volatile 型变量的特殊规则
        • long 和 double 的非原子性协定
        • 原子性、可见性与有序性
        • 先行发生(Happens-Before)原则
      • 线程
        • 状态转换
        • 线程安全性
          • 对象的共享
            • 可见性
            • 线程封闭
            • 不可变性
            • 安全发布
          • 在现有的线程安全类中添加功能
        • 线程池
          • Executor 框架
          • ExecutorService
          • Executors
          • Future
          • CompletionService
          • 设置线程池的大小
          • ThreadPoolExecutor
      • 线程安全的容器
        • 同步容器类
        • 并发容器
          • ConcurrentHashMap
          • CopyOnWriteArrayList
          • BlockingQueue
            • 串行线程封闭
            • 双端队列与工作密取
      • 任务取消
        • 自定义的取消标志
        • 线程中断
        • 通过 Future 来实现取消
      • 条件队列
        • 内置条件队列
        • 显式的 Condition 对象
      • JUC 中的 AQS
        • AbstractQueuedSynchronizer
        • ReentrantLock
        • ReentrantReadWriteLock
        • Semaphore
        • CountDownLatch
      • 原子变量
        • CAS
        • 原子变量类
        • ABA 问题
        • 非阻塞算法
          • 非阻塞的栈
          • 非阻塞的链表(X)
    • Java 虚拟机
      • JVM 的运行机制
      • 类加载器
      • 运行时数据区
        • JVM 的内存区域
        • 永久代与元空间
        • OutOfMemoryError
      • Java 中的 4 种引用类型
      • 垃圾收集(GC)
        • 如何确定垃圾
        • 垃圾回收算法
        • 垃圾收集器
          • Serial 收集器
          • ParNew 收集器
          • Parallel Scavenge 收集器
          • Serial Old 收集器
          • Parallel Old 收集器
          • CMS 收集器
          • Garbage First 收集器
  • Group 1
    • JDK 与 JRE
    • JVM默认配置
    • java与HTTPS
    • 构建高效且可伸缩的结果缓存
    • 基础补充
      • 在 Switch 中使用 String
      • 为什么 Java 语言不支持多重继承?
      • 为什么在重写 equals 方法的时候需要重写 hashCode 方法
      • 为什么 String 要设计为不可变的?
      • 移位运算符
      • SPI 机制
      • 为何 HashMap 不是线程安全的
      • Class.forName() 和ClassLoader.loadClass() 区别
      • synchronized 关键字
    • 零拷贝
    • Java中的锁优化技术
      • 自旋锁与自适应自旋
      • 锁消除
      • 锁粗化
      • 轻量级锁
      • 偏向锁
    • Arthas
    • Thread.sleep()、Object.wait()、Condition.await()、LockSupport.park()
由 GitBook 提供支持
在本页
  1. 基础知识
  2. Java 并发
  3. 线程安全的容器

同步容器类

同步容器类包括 Vector 和 Hashtable,二者是早期 JDK 的一部分,此外还包括在 JDK l.2 中添加的一些功能相似的类,这些同步的封装器类是由 Collections.synchronizedXxx 等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。

迭代器与 ConcurrentModificationException

在设计同步容器类的迭代器时并没有考虑到并发修改的问题,并且它们表现出的行为是“及时失败”(fail-fast)的。这意味着,当它们发现容器在迭代过程中被修改时,就会抛出一个ConcurrentModificationException 异常。

这种“及时失败”的迭代器并不是一种完备的处理机制,而只是“善意地”捕获并发错误,因此只能作为并发问题的预警指示器。它们采用的实现方式是,将计数器的变化与容器关联起来:如果在迭代期间计数器被修改,那么 hasNext 或 next 将抛出 ConcurrentModificationException。然而,这种检查是在没有同步的情况下进行的,因此可能会看到失效的计数值,而迭代器可能并没有意识到已经发生了修改。这是一种设计上的权衡,从而降低并发修改操作的检测代码对程序性能带来的影响。

在单线程代码中也可能抛出 ConcurrentModificationException 异常。当对象直接从容器中删除而不是通过 Iterator.remove 来删除时,就会抛出这个异常。

容器的 hashCode、equals、containsAll、removeAll 和 retainAll 等方法,以及把容器作为参数的构造函数,都会对容器进行迭代。

所有这些间接的迭代操作都可能抛出 ConcurrentModificationException。

Vector

在 Vector 中的 modCount 类似于“代”的概念,任何修改操作都会产生一个 modCount 自增,而 modCount 的值并不含有元素数量的含义。

public class Vector<E> {
    protected transient int modCount = 0;
    
    public boolean addAll(Collection<? extends E> c) {
        modCount++;
        ...
    }
    
    public synchronized E remove(int index) {
        modCount++;
        ...
    }

    ...
    
    private class Itr implements Iterator<E> {
        int expectedModCount = modCount;

        public E next() {
            synchronized (Vector.this) {
                checkForComodification();
                ...
            }
        }

        public void remove() {
            synchronized (Vector.this) {
                checkForComodification();
                ...
            }
            ...
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}
上一页线程安全的容器下一页并发容器

最后更新于9个月前