一、CopyOnWriteArrayList
ArrayList对应的线程安全的并发容器是CopyOnWriteArrayList
HashSet对应的线程安全的并发容器是CopyOnWriteArraySet
CopyOnWriteArraySet与CopyOnWriteArrayList雷同
例如
private static List<Integer> list = new CopyOnWriteArrayList<>();
写操作的步骤:
1.先拷贝一份原来的list 为 listNew
2.在新的数组listNew上做写操作
3.写完后将原来的数组list 指向新的数组listNew
CopyOnWriteArrayList的add操作都是在锁的保护下进行的,避免多线程并发做写操作时复制出多个副本。
读操作:
是在原数组上读,不需要拷贝。
缺点:
1.由于写操作要拷贝数组,会消耗内存。如果数组比较大,可能会导致young GC或者 full GC。
2.不适合于实时读的场景。读的数据可能会是久的,因为步骤123需要花时间。CopyOnWriteArrayList只能做到最终一致性,无法满足实时性。它更适合读多、写少,的场景。
代码示例:
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class CopyOnWriteArrayListExample {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
private static List<Integer> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(() -> {
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", list.size());
}
private static void update(int i) {
list.add(i);
}
}
结果是线程安全的
最后的size结果是5000
二、ConcurrentSkipListSet
TreeSet对应的线程安全的并发容器是ConcurrentSkipListSet。
ConcurrentSkipListSet跟TreeSet一样支持自然排序的。
其中单个操作,比如add,是线程安全的,但是批量操作addAll并不能保证一批操作是原子执行的。
不支持Null元素。