集合中的线程安全问题

前言

早在刚开始学习java se的时候,就遇到过这样问题,xxx是线程安全的,xxxx是线程不安全的,当时也是一脸蒙,只记住了结论。后来接触了单例才有一点了解

线程安全的:
vector、hashtable、stringbuffer等

非线程安全的:
arraylist、linkedlist、hashmap、StringBuilder等等

首先来模拟一个案例

测试集合类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void test() {
// 用来测试的List
List<Object> list = new ArrayList<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());
}

自定义线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.zwl.test;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class Mythread implements Runnable {
private List<Object> list;
private CountDownLatch countDownLatch;
public Mythread(List<Object> list,CountDownLatch countDownLatch){
this.list = list;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// 每个线程向List中添加100个元素
for(int i = 0; i < 100; i++)
{
list.add(new Object());
}
// 完成一个子线程
countDownLatch.countDown();
}
}

测试类:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
// 进行10次测试
for(int i = 0; i < 10; i++)
{
test();
}
}

测试结果:

99873
99968
99955
100000
100000
100000
100000
Exception in thread “Thread-7516” java.lang.ArrayIndexOutOfBoundsException: 76327
at java.util.ArrayList.add(ArrayList.java:441)
at com.zwl.test.Mythread.run(Mythread.java:20)
at java.lang.Thread.run(Thread.java:744)

有时不会出现异常,但是list的长度不是最终期望的那样,显然是线程不安全的,
这里是arraylist,同样将其换成linkedlist,也可以改变list,变成其它,得到同样线程不安全结果

测试结果:

100000
99783
100000
99945
99897
99996
99879
99971
100000
99899

将其换成线程安全的,如

List list = new Vector();

测试结果:

100000
100000
100000
100000
100000
100000
100000
100000
100000
100000

当然集合中的线程不安全是多线程同时操作同一个对象,如果是不同类,就不存在线程安全问题

对于线程安全,一般通过线程同步来实现,即通过申明synchronized关键字

如,改一下之前的线程安全的例子number类

修改后的number类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.zwl.test;
public class number {
private int num=0;
public void count(){
num++;
}
public int getnumber(){
return num;
}
}

然后是自定义线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Mythread implements Runnable {
private number num;
private CountDownLatch countDownLatch;
public Mythread(number num,CountDownLatch countDownLatch){
this.num = num;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
for(int i = 0; i < 100; i++)
{
num.count();
}
// 完成一个子线程
countDownLatch.countDown();
}
}

线程测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void test() {
number num=new number();
// 线程数量(1000)
int threadCount = 1000;
// 用来让主线程等待threadCount个子线程执行完毕
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for(int i = 0; i < threadCount; i++)
{
Thread thread = new Thread(new Mythread(num,countDownLatch));
thread.start();
}
try
{
// 主线程等待所有子线程执行完成,再向下执行
countDownLatch.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(num.getnumber());
}

测试类:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
// 进行10次测试
for(int i = 0; i < 10; i++)
{
test();
}
}

测试结果:

100000
100000
100000
100000
99940
99300
100000
100000
100000
100000

出现了线程不安全
可以通过对方法声明同步

public synchronized void count(){}

最后输出的书都是同一个数

最后不得不说,线程安全是一个很复杂的问题,有时候不注意就会出现数据上的各种问题!!!

热评文章