MST

星途 面试题库

面试题:Java Collections工具类在处理线程安全集合时的技巧与原理

在多线程环境下,需要使用线程安全的集合。请说明如何利用Collections工具类将一个普通的非线程安全集合(如ArrayList)转换为线程安全集合,并详细阐述这种转换后的线程安全机制,以及可能存在的性能问题和解决方案。
43.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试
  1. 将普通集合转换为线程安全集合的方法
    • 可以使用Collections.synchronizedList方法将ArrayList转换为线程安全集合。示例代码如下:
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class ThreadSafeListExample {
        public static void main(String[] args) {
            List<String> nonThreadSafeList = new ArrayList<>();
            List<String> threadSafeList = Collections.synchronizedList(nonThreadSafeList);
        }
    }
    
  2. 线程安全机制
    • Collections.synchronizedList返回的是一个SynchronizedRandomAccessList(如果原列表实现了RandomAccess接口)或SynchronizedList
    • 这些类在其方法(如addgetremove等)中使用了synchronized关键字来同步访问。例如,SynchronizedListadd方法可能类似这样实现:
    public boolean add(E e) {
        synchronized (mutex) {
            return list.add(e);
        }
    }
    
    • 这里的mutex是用于同步的对象,通常是返回的同步列表本身。这意味着在同一时间,只有一个线程可以访问列表的方法,从而保证了线程安全。
  3. 性能问题
    • 锁竞争开销:由于所有对列表的操作都需要获取锁,在高并发环境下,多个线程频繁竞争锁会导致性能瓶颈。例如,一个线程在执行add操作时持有锁,其他线程想要执行get操作也需要等待锁的释放,即使get操作本身并不需要修改列表状态,这就造成了不必要的等待。
    • 可伸缩性问题:随着线程数量的增加,锁竞争会变得更加激烈,系统的可伸缩性会受到影响。
  4. 解决方案
    • 使用CopyOnWriteArrayList
      • CopyOnWriteArrayList适用于读多写少的场景。它的原理是在进行写操作(如addremove)时,会复制一份原数组,在新数组上进行修改,然后将原引用指向新数组。读操作(如get)则直接读取原数组,不需要加锁。
      • 示例代码:
      import java.util.List;
      import java.util.concurrent.CopyOnWriteArrayList;
      
      public class CopyOnWriteArrayListExample {
          public static void main(String[] args) {
              List<String> list = new CopyOnWriteArrayList<>();
              list.add("element");
              String element = list.get(0);
          }
      }
      
    • 使用ConcurrentLinkedQueue
      • 对于队列操作,ConcurrentLinkedQueue是一个线程安全的无界队列。它采用无锁数据结构,使用CAS(Compare - And - Swap)操作来实现线程安全。适用于需要高效的并发队列操作场景,如生产者 - 消费者模型。
      • 示例代码:
      import java.util.concurrent.ConcurrentLinkedQueue;
      
      public class ConcurrentLinkedQueueExample {
          public static void main(String[] args) {
              ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
              queue.add("element");
              String element = queue.poll();
          }
      }
      
    • 读写锁(ReadWriteLock
      • 如果需要更细粒度的控制,可以使用ReadWriteLock。读操作可以并发执行,而写操作需要独占锁。例如,对于一个自定义的基于ArrayList的集合,可以使用ReadWriteLock来控制访问。
      • 示例代码:
      import java.util.ArrayList;
      import java.util.List;
      import java.util.concurrent.locks.ReadWriteLock;
      import java.util.concurrent.locks.ReentrantReadWriteLock;
      
      public class ReadWriteLockList {
          private final List<String> list = new ArrayList<>();
          private final ReadWriteLock lock = new ReentrantReadWriteLock();
      
          public void add(String element) {
              lock.writeLock().lock();
              try {
                  list.add(element);
              } finally {
                  lock.writeLock().unlock();
              }
          }
      
          public String get(int index) {
              lock.readLock().lock();
              try {
                  return list.get(index);
              } finally {
                  lock.readLock().unlock();
              }
          }
      }