多线程编程(二):List组装

在日常工作中,难免遇到在循环体中组装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。

 

至于锁是个什么东西,后续会花大功夫详细说明。

我是银河架构师,十年饮冰,难凉热血,愿历尽千帆,归来仍是少年! 

如果文章对您有帮助,请举起您的小手,轻轻【三连】,这将是笔者持续创作的动力源泉。当然,如果文章有错误,或者您有任何的意见或建议,请留言。感谢您的阅读!

你可能感兴趣的:(多线程,日积月累,java,并发编程,多线程)