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