付春杰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
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>