在日常工作中,难免遇到在循环体中组装List的场景,一般来说,不考虑多线程的话,实现起来非常简单。
比如,拟定一个场景,给定0-9999,共10000个数字,组装2个列表:奇数列表、偶数列表,分别存储到2个列表中。
单线程的写法非常简单,一般也都会这么写:
public void test3() throws Exception {
List odd = new ArrayList<>();
List even = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
int q = i % 2;
if (q == 0) {
even.add(i);
} else {
odd.add(i);
}
}
System.out.println("odd.size() -> " + odd.size());
System.out.println("odd -> " + odd);
System.out.println("even.size() -> " + even.size());
System.out.println("even -> " + even);
}
运行这段代码,得出的结果也必然是,奇数列表5000个,偶数列表5000个。
但是,如果有天,业务量激增,单线程已经满足不了需求了,必须使用多线程提升处理速度,那么多线程该怎么处理呢?
自然而然的就会想到如下的写法:
public void test4() throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 50L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
CountDownLatch countDownLatch = new CountDownLatch(10000);
List odd = new ArrayList<>();
List even = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
final int j = i;
executor.submit(() -> {
try {
int q = j % 2;
if (q == 0) {
even.add(j);
} else {
odd.add(j);
}
} catch (Exception e) {
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
System.out.println("odd.size() -> " + odd.size());
System.out.println("odd -> " + odd);
System.out.println("even.size() -> " + even.size());
System.out.println("even -> " + even);
}
但是,运行结果却不尽如人意,奇数和偶数列表,并不一定是5000,飘忽不定。
什么原因呢?
多线程场景下,有可能同时几个线程同时往列表中add数据。这样一来,就会丢失(覆盖)一部分数据。
这就是典型的多线程问题。而ArrayList恰巧不是线程安全的。
此时,就需要引入一个重要类:CopyOnWriteArrayList。这个类是线程安全的,add方法会使用锁,前面一个线程释放锁之后,后一个线程才能继续add操作。因此,不会出现丢失/覆盖数据的情况。
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();
}
}
那么,在前面方法的基础上,修改列表类型为CopyOnWriteArrayList。
public void test5() throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 50L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
CountDownLatch countDownLatch = new CountDownLatch(10000);
List odd = new CopyOnWriteArrayList<>();
List even = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
final int j = i;
executor.submit(() -> {
try {
System.out.println("Thread -> " + Thread.currentThread().getName() + " i -> " + j);
Thread.sleep(50);
int q = j % 2;
if (q == 0) {
even.add(j);
} else {
odd.add(j);
}
} catch (Exception e) {
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
System.out.println("odd.size() -> " + odd.size());
System.out.println("odd -> " + odd);
System.out.println("even.size() -> " + even.size());
System.out.println("even -> " + even);
}
运行一下程序,奇数和偶数列表,必然都是5000。
至于锁是个什么东西,后续会花大功夫详细说明。
我是银河架构师,十年饮冰,难凉热血,愿历尽千帆,归来仍是少年!
如果文章对您有帮助,请举起您的小手,轻轻【三连】,这将是笔者持续创作的动力源泉。当然,如果文章有错误,或者您有任何的意见或建议,请留言。感谢您的阅读!