每天学习一点点之guava篇【ImmutableList】

我们在使用ArrayList的时候,经常被并发的问题所困扰,这个时候我们通常会去找一个线程安全的列表,例如我们熟知的SynchronizedListCopyOnWriteArrayListVector

SynchronizedList

SynchronizedList通过synchronized关键字来保证变更过程是同步执行的

java 复制代码
static class SynchronizedList<E> extends SynchronizedCollection<E>
    implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;

    final List<E> list;

    SynchronizedList(List<E> list) {
        super(list);
        this.list = list;
    }
    SynchronizedList(List<E> list, Object mutex) {
        super(list, mutex);
        this.list = list;
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        synchronized (mutex) {return list.equals(o);}
    }
    public int hashCode() {
        synchronized (mutex) {return list.hashCode();}
    }

    public E get(int index) {
        synchronized (mutex) {return list.get(index);}
    }
    public E set(int index, E element) {
        synchronized (mutex) {return list.set(index, element);}
    }
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);}
    }
    public E remove(int index) {
        synchronized (mutex) {return list.remove(index);}
    }

    public int indexOf(Object o) {
        synchronized (mutex) {return list.indexOf(o);}
    }
    public int lastIndexOf(Object o) {
        synchronized (mutex) {return list.lastIndexOf(o);}
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        synchronized (mutex) {return list.addAll(index, c);}
    }

2、CopyOnWriteArrayList

CopyOnWriteArrayListjava.util.concurrent包中的一个线程安全列表实现。它通过在每次修改时创建底层数组的新副本来确保线程安全。这种实现适用于读多写少的场景,因为写操作的开销较大,而读操作则无需加锁。 这是CopyOnWriteArrayList的set方法源码

java 复制代码
/**
 * 替换列表中指定位置的元素
 * 
 * @param index 要替换的元素索引
 * @param element 要存储的新元素
 * @return 被替换的旧元素
 */
public E set(int index, E element) {
    // 获取重入锁实例
    final ReentrantLock lock = this.lock;
    // 获取锁,确保线程安全
    lock.lock();
    try {
        // 获取当前数组的引用
        Object[] elements = getArray();
        // 获取指定索引位置的旧值
        E oldValue = get(elements, index);

        // 只有当新值与旧值不同时才创建新数组
        if (oldValue != element) {
            // 获取数组长度
            int len = elements.length;
            // 创建一个新数组,复制所有元素
            Object[] newElements = Arrays.copyOf(elements, len);
            // 在新数组中设置新值
            newElements[index] = element;
            // 更新内部数组引用为新数组
            setArray(newElements);
        } else {
            // 即使值相同也要调用setArray
            // 这确保了volatile写入语义,维护了内存可见性
            setArray(elements);
        }
        // 返回被替换的旧值
        return oldValue;
    } finally {
        // 无论操作是否成功,都释放锁
        lock.unlock();
    }
}

3、Vector

Vector是Java早期提供的一种线程安全的列表实现,所有操作都加锁。由于所有操作都进行了同步,Vector的性能在现代应用中通常不如其他更轻量级的同步策略。

ImmutableList的特点是什么以及为什么要选择它

ImmutableList是Guava库中的一个不可变集合类,它是java.util.List接口的不可变实现,实现简单可靠。正如名称所示,一旦创建,ImmutableList的元素无法被改变。这种设计使得它在多线程环境下尤其有用,可以像String一样安全地被共享。

ImmutableList是如何保证里面的元素是不可变的

ImmutableList(以Guava库中的实现为例)通过多种机制确保其不可变性:

禁止修改操作

所有修改集合的方法(如addremoveset等)都会抛出UnsupportedOperationException

内部数据结构保护

内部数组是private final的 没有提供修改内部状态的方法

防御性复制

创建时对输入集合进行复制,而不是引用 返回内部元素时进行保护性处理

不可变视图

返回的子列表、迭代器等都是不可变的

但是需要注意的是ImmutableList只能保证列表结构不可变,不能保证列表中对象的内部状态不变。如果列表包含可变对象,这些对象的内部状态仍可被修改。

最后简单看一下ImmutableList的源码

java 复制代码
ImmutableList.java
/**
 * 将对象数组转换为不可变列表
 * 
 * @param elements 源数组
 * @param length 要使用的元素数量
 * @return 包含指定元素的不可变列表
 */
static <E> ImmutableList<E> asImmutableList(Object[] elements, int length) {
    switch (length) {
      case 0:
        // 空列表情况
        return of();
      case 1:
        // 单元素列表使用专门的实现以提高效率
        @SuppressWarnings("unchecked") // collection had only Es in it
        ImmutableList<E> list = new SingletonImmutableList<E>((E) elements[0]);
        return list;
      default:
        // 如果指定长度小于数组实际长度,创建一个新数组
        if (length < elements.length) {
          elements = arraysCopyOf(elements, length);
        }
        // 创建标准不可变列表
        return new RegularImmutableList<E>(elements);
    }
  }


  /**
   * 返回从指定位置开始的不可修改列表迭代器
   */
  @Override
  public UnmodifiableListIterator<E> listIterator(int index) {
    return new AbstractIndexedListIterator<E>(size(), index) {
      @Override
      protected E get(int index) {
        return ImmutableList.this.get(index);
      }
    };
  }

  /**
   * 查找指定对象在列表中第一次出现的索引
   * 如果对象为null或不存在则返回-1
   */
  @Override
  public int indexOf(@Nullable Object object) {
    return (object == null) ? -1 : Lists.indexOfImpl(this, object);
  }

  /**
   * 查找指定对象在列表中最后一次出现的索引
   * 如果对象为null或不存在则返回-1
   */
  @Override
  public int lastIndexOf(@Nullable Object object) {
    return (object == null) ? -1 : Lists.lastIndexOfImpl(this, object);
  }

  /**
   * 检查列表是否包含指定对象
   */
  @Override
  public boolean contains(@Nullable Object object) {
    return indexOf(object) >= 0;
  }

  /**
   * 返回指定范围内元素的不可变列表视图
   * 从fromIndex(包含)到toIndex(不包含)
   * 如果fromIndex和toIndex相等,则返回空列表
   */
  @Override
  public ImmutableList<E> subList(int fromIndex, int toIndex) {
    // 检查索引范围是否有效
    checkPositionIndexes(fromIndex, toIndex, size());
    int length = toIndex - fromIndex;
    if (length == size()) {
      // 如果范围覆盖整个列表,直接返回this
      return this;
    }
    switch (length) {
      case 0:
        // 空子列表
        return of();
      case 1:
        // 单元素子列表
        return of(get(fromIndex));
      default:
        // 多元素子列表
        return subListUnchecked(fromIndex, toIndex);
    }
  }

  /**
   * 由subList方法在toIndex-fromIndex>1时调用
   * 此时索引验证已经完成
   */
  ImmutableList<E> subListUnchecked(int fromIndex, int toIndex) {
    return new SubList(fromIndex, toIndex - fromIndex);
  }

  /**
   * 子列表实现,作为原列表的视图
   */
  class SubList extends ImmutableList<E> {
    // 在原列表中的偏移量
    final transient int offset;
    // 子列表的长度
    final transient int length;

    SubList(int offset, int length) {
      this.offset = offset;
      this.length = length;
    }

    @Override
    public int size() {
      return length;
    }

    @Override
    public E get(int index) {
      // 检查索引是否在范围内
      checkElementIndex(index, length);
      // 转换为原列表中的索引并获取元素
      return ImmutableList.this.get(index + offset);
    }

    @Override
    public ImmutableList<E> subList(int fromIndex, int toIndex) {
      // 检查索引范围是否有效
      checkPositionIndexes(fromIndex, toIndex, length);
      // 转换为原列表的索引并创建子列表
      return ImmutableList.this.subList(fromIndex + offset, toIndex + offset);
    }

    @Override
    boolean isPartialView() {
      // 子列表总是部分视图
      return true;
    }
  }

  /**
   * 不支持的操作 - 在指定位置添加集合元素
   * 保证抛出异常并保持列表不变
   *
   * @throws UnsupportedOperationException 总是抛出
   * @deprecated 不支持的操作
   */
  @CanIgnoreReturnValue
  @Deprecated
  @Override
  public final boolean addAll(int index, Collection<? extends E> newElements) {
    throw new UnsupportedOperationException();
  }

  /**
   * 不支持的操作 - 替换指定位置的元素
   * 保证抛出异常并保持列表不变
   *
   * @throws UnsupportedOperationException 总是抛出
   * @deprecated 不支持的操作
   */
  @CanIgnoreReturnValue
  @Deprecated
  @Override
  public final E set(int index, E element) {
    throw new UnsupportedOperationException();
  }

  /**
   * 不支持的操作 - 在指定位置插入元素
   * 保证抛出异常并保持列表不变
   *
   * @throws UnsupportedOperationException 总是抛出
   * @deprecated 不支持的操作
   */
  @Deprecated
  @Override
  public final void add(int index, E element) {
    throw new UnsupportedOperationException();
  }

  /**
   * 不支持的操作 - 移除指定位置的元素
   * 保证抛出异常并保持列表不变
   *
   * @throws UnsupportedOperationException 总是抛出
   * @deprecated 不支持的操作
   */
  @CanIgnoreReturnValue
  @Deprecated
  @Override
  public final E remove(int index) {
    throw new UnsupportedOperationException();
  }
相关推荐
写bug写bug18 分钟前
彻底搞懂 RSocket 协议
java·后端
就是我18 分钟前
轻松管理Linux定时任务:Cron实用教程
linux·后端
橘子青衫22 分钟前
深入理解Callable与Future:实现Java多线程中的异步任务处理
java·后端
bobz96540 分钟前
libvirt 相关 sock 整理
后端
Asthenia04122 小时前
ElasticSearch8.x+SpringBoot3.X联调踩坑指南
后端
gou123412342 小时前
【Golang进阶】第八章:并发编程基础——从Goroutine调度到Channel通信实战
开发语言·后端·golang
程序小武2 小时前
python编辑器如何选择?
后端·python
陈随易2 小时前
薪资跳动,VSCode实时显示今日打工收入
前端·后端·程序员
失乐园2 小时前
电商/物流/IoT三大场景:用MongoDB设计高扩展数据架构的最佳实践
java·后端·架构
五行星辰2 小时前
Spring AI 实战:用 Java 搞 AI,从此告别调参侠
java·后端