1.引言
在前两篇博客中主要记录了多线程编程中存在的线程安全问题,我们可以通过加锁的方式实现线程同步。在JDK中给我们提供了多种API,这些API 有一部分是非线程安全的。比如:ArrayList
和SimpleDateFormat
,在本篇博客中就主要介绍一下这两种API存在的线程安全问题,以及应该如何去解决这种线程安全问题。
2.ArrayList
线程安全问题
2.1 ArrayList
存在的线程安全问题
- 首先我们先看一个代码,(从线程中给数组arr添加一个值
10
)
import java.util.List;
public class MyThread extends Thread {
private List arr;
public MyThread(List arr) {
this.arr = arr;
}
@Override
public void run() {
try {
Thread.sleep(500);
arr.add("10");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 我们启动十个线程
import java.util.ArrayList;
import java.util.List;
public class app {
public static void main(String[] args) throws InterruptedException {
List arr=new ArrayList();
for(int i=0;i<10;i++)
{
MyThread td=new MyThread(arr);
td.start();
}
Thread.sleep(5000);
System.out.println(arr.size());
}
}
- 运行结果(出现问题)
将代码运行之后,和我们预期的不太一样,我们认为,arr
数组的size为10
,并且arr
的前十个元素是10
,但是呢?arr
数组的前两个数值却为null
,这个bug的产生的原因便是:ArrarList
这个集合类是线程不安全的,add方法并没有添加synchronized
关键字,按照我们上一篇博客中说的,如果想要add数组线程安全,给add方法添加一个synchronized
关键字即可,但是我们拿不到ArrarList
源码,这就导致了我们不可以直接去更改add的原方法。
如何去解决这个问题呢?接下来我这里提供两种方法解决线程安全问题。
2.2 通过内部类解决线程安全问题(这里以add方法为例)
- 首先我们新建一个类叫做,
MyCollection
(用于装饰ArrayList)
import java.util.List;
class MyCollection{
public static List synList(List list){
return new MyList(list);
}
private static class MyList implements List{
private List list;
MyList(List list){
this.list = list;
}
public synchronized boolean add(Object obj){
return list.add(obj);
}
// .....还有一些List需要实现的方法
}
}
- 然后线程类不修改,我们的main函数变成了这样
import java.util.ArrayList;
import java.util.List;
public class app {
public static void main(String[] args) throws InterruptedException {
List arr=MyCollection.synList(new ArrayList());
for(int i=0;i<10;i++)
{
MyThread td=new MyThread(arr);
td.start();
}
Thread.sleep(5000);
System.out.println(arr.size());
}
}
- 运行结果
其实这里的方法也叫做装饰设计模式,不继承arrarylist类,但是我修改了ArrayList里面的方法原有的逻辑(即:将线程不安全的add方法修改为了线程安全的)
2.3 通过JDK提供我们的api将ArrayList修改为线程安全的
- 我们刚刚新建了一个类叫做
MyCollection
,其实JDK提供了一个类Collections
,可以直接将ArrayList
转换成线程安全的
List arr=Collections.synchronizedList(new ArrayList());
原理和我们上面写的类似,将ArrayList变成了线程安全的。
3.SimpleDateFormat
存在的线程安全问题
SimpleDateFormat
和ArrayList
一样,同样是线程非安全的,但是不同的是SimpleDateFormat
是一个工具类,ArrayList
是一个集合类,集合往往存储的是数据,工具类主要是用来处理数据,那么对于工具类,处理线程安全问题有着更加简单的方法。
3.1 SimpleDateFormat
线程非安全
首先我们看一下SimpleDateFormat
这个类是如何出现线程非安全的。
- 创建我们的线程类
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class MyThread extends Thread {
private SimpleDateFormat sdf;
private String datestring;
public MyThread(SimpleDateFormat sdf,String datestring) {
this.sdf=sdf;
this.datestring=datestring;
}
@Override
public void run() {
try {
//将传进来的字符串转换成date对象
Date date=sdf.parse(datestring);
//将传进来的date对象重新转换成String
String result=sdf.format(date).toString();
//按理说,这里应该是一样的字符串
System.out.println(datestring+":"+result);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- main函数中的代码
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class app {
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
String [] s=new String[]{
"1999-10-11",
"1999-10-12",
"1999-10-13",
"1999-10-14",
"1999-10-15"
};
for(int i=0;i<5;i++)
{
MyThread td=new MyThread(sdf,s[i]);
td.start();
}
Thread.sleep(5000);
}
}
- 运行结果(注意 我们的代码是没错误的)
这种问题的产生也是线程的非安全性产生的,如果我们想和ArrayList一样,就需要去改造parse
方法,但是注意。这里是工具类,又不是数据,我们有着更加简单的方法解决这种线程问题。
3.2 使用多个SimpleDateFormat
对象
- 我们将main函数中改成这样
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class app {
public static void main(String[] args) throws InterruptedException {
String [] s=new String[]{
"1999-10-11",
"1999-10-12",
"1999-10-13",
"1999-10-14",
"1999-10-15"
};
for(int i=0;i<5;i++)
{
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
MyThread td=new MyThread(sdf,s[i]);
td.start();
}
Thread.sleep(5000);
}
}
- 运行结果:没有问题了
4.总结
在此篇博客中主要介绍了:
- 如果是工具类的线程非安全性问题,我们如何解决?(当无法修改源码的时候)
- 如果是数据集合类的线程非安全性问题,我们应该如何去解决?(当无法修改源码的时候)