线程安全

付春杰Blog 2020-02-17

什么是线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

例如:StringBuffer是线程安全的,StringBuilder不是线程安全的。HashTable是线程安全的,HashMap不是线程安全的。Vector是线程安全的,LinkedList和ArrayLis不是线程安全的。

下面的代码同时开启1000个线程,在list集合中同时插入100个元素,最后查看list中元素个数:

class MyThread implements Runnable
{
    private List<Object> list;

    private CountDownLatch countDownLatch;

    public MyThread(List<Object> list, CountDownLatch countDownLatch)
    {
        this.list = list;
        this.countDownLatch = countDownLatch;
    }

    public void run()
    {
        // 每个线程向List中添加100个元素
        for(int i = 0; i < 100; i++)
        {
            list.add(new Object());
        }

        // 完成一个子线程
        countDownLatch.countDown();
    }
}


public class Test
{

    public static void test()
    {
        // 用来测试的List
        List<Object> list = new LinkedList<Object>();

        // 线程数量(1000)
        int threadCount = 1000;

        // 用来让主线程等待threadCount个子线程执行完毕
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        // 启动threadCount个子线程
        for(int i = 0; i < threadCount; i++)
        {
            Thread thread = new Thread(new MyThread(list, countDownLatch));
            thread.start();
        }

        try
        {
            // 主线程等待所有子线程执行完成,再向下执行
            countDownLatch.await();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        // List的size
        System.out.println(list.size());
    }

    public static void main(String[] args)
    {
        // 进行10次测试
        for(int i = 0; i < 10; i++)
        {
            test();
        }
    }
}

当list的实现类是ArrayList时,运行结果为:

java.lang.ArrayIndexOutOfBoundsException。这验证了ArrayList在并发访问的过程中可能会出现数组越界异常。

当list的实现类是LinkedList时,运行结果为:

98742
93685
92327
96579
96509
97374
98437
98117
97975
98180

最后list的元素个数小于100000个。

当list的实现类是ArrayList时,运行结果为:


100000
100000
100000
100000
100000
100000
100000
100000
100000
100000

说明只有Vector是线程安全的。

关于线程同步工具类CountDownLatch

”允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier 。A CountDownLatch是一种通用的同步工具,可用于多种用途。 一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。 一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。CountDownLatch一个有用的属性是,它不要求调用countDown线程等待计数到达零之前继续,它只是阻止任何线程通过await ,直到所有线程可以通过。“

摘自CountDownLatch的官方API。

在上面的例子中,主线程调用countDownLatch.await(),会陷入阻塞,直到其他线程执行1000次countDownLatch.countDown()才会继续往下执行。

扩展:

如果子进程在执行子任务的过程中,它无法完成任务的提交,即无法执行countDownLatch.countDown(),那么主线程会陷入无限等待。

这个时候,可以在await中加入一个超时参数,主线程等待预定的时间会重新激活。<span><a target="_blank" href="https://www.ancii.com/link/v1/is42amo4H0nIpfwLzpVp5EkrWJ1FXGFAKdHH_b9or-aIqX1ZE-75SjiIxG3m6rmoluKdJGRzOc37Zrw9OG95ZdohP1ppPERSSaq3cUY2glI8dvFyPKZCzEWpeoEaej61g5w51f7oSzc71APog6xc-QBtww_U22zacm029EyZUek/" rel="nofollow" title="await">await</a>(long timeout, <a target="_blank" href="https://www.ancii.com/link/v1/is42amo4H0nIpfwLzpVp5EkrWJ1FXGFAKdHH_b9or-aIqX1ZE-75SjiIxG3m6rmovJoojewmF3bpV1NU5jcpZAy11hiRXbaYdQMsliWmcfo/" rel="nofollow" title="TimeUnit">TimeUnit</a> unit)</span>

相关推荐