Version: Next

集合的线程安全性

List——CopyOnWriteArrayList

默认线程不安全

public class Demo01List {
public static void main(String[] args) {
List<String> list = Arrays.asList("1", "2", "3");
list.forEach(System.out::println);
}
}
1
2
3

写一个多线程的例子

public class Demo01List {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i < 200; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + " | " + list);
}, "Thread" + i).start();
}
}
}

会出现报错ConcurrentModificationException——并发修改异常

Exception in thread "Thread190" Exception in thread "Thread184" java.util.ConcurrentModificationException

解决方案

  1. 使用Vector,默认线程安全,基于synchronized实现,但是这东西效率不行,没人用

    • Vector出现在JDK1.0
    • ArrayList出现在JDK1.2
  2. 使用Collections工具类,Collections.synchronizedXXX集合

  3. 使用JUC的CopyOnWriteArrayList,基于Lock锁实现(1.8)、JDK11里又弄成synchronized实现了

    • 底层使用了transientvolatile
    • 写入时复制、COW(Copy On Write),一种效率优化策略 (引出新知识点,读写分离,Mycat等)
      • 读取,是固定的
      • 写入,可能发生后写的把前面的东西覆盖了,解决方法:
        • 复制一份,让调用者写,写好了再放回去
    • 关键源码
    public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    Object[] elements = getArray();
    int len = elements.length;
    Object[] newElements = Arrays.copyOf(elements, len + 1); // 先复制
    newElements[len] = e; // 在复制的上面写
    setArray(newElements); // 写完再扔回去
    return true;
    } finally {
    lock.unlock();
    }
    }
  • 使用Collections工具类,实现线程安全
@Test
public void Test2() {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i < 200; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + " | " + list);
}, "Thread" + i).start();
}
}
  • 使用CopyOnWriteArrayList
@Test
public void Test3() {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i < 200; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + " | " + list);
}, "Thread" + i).start();
}
}

为什么用CopyOnWriteArrayList,而不用Vectoc?

  • Vector基于synchronized;CopyOnWriteArrayList基于Lock锁

Set——CopyOnWriteSet

  • HashSet
@Test
public void Test1() {
Set<String> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + set);
}, "Thread" + i).start();
}
}

报错

  • Collections.synchronizedSet
@Test
public void Test2() {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 200; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + set);
}, "Thread" + i).start();
}
}
  • CopyOnWriteSet
@Test
public void Test3() {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 200; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + set);
}, "Thread" + i).start();
}
}

Map——ConcurrentHashMap

  • HashMap
@Test
public void test1() {
Map<String, String> map = new HashMap();
for (int i = 0; i < 150; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),
UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, "Thread" + i).start();
}
}
  • Collections.synchronizedMap
@Test
public void test2() {
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
for (int i = 0; i < 150; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, "Thread" + i).start();
}
}
  • ConcurrentHashMap
@Test
public void test3() {
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 150; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, "Thread" + i).start();
}
}

实现原理——分段锁