2019独角兽企业重金招聘Python工程师标准>>>
现在在CPU上,摩尔定律已经失效,大家都不再追求高频率,而是越来越追求多核,阿姆达尔定律似乎更重要一些了。
并发有很多优点,包括充分利用CPU的多核提高性能,更简单的模型(跟异步的callback hell相比)以及响应更灵敏得GUI等。
但是并发也引出很多问题,最重要的是安全性,很多在单线程环境下理所当然正确的程序在并发下都变得不正确,为了写出并发安全的程序,需要付出更多的努力。本文主要介绍一下并发问题的根本原因,以及针对策略。
来看下面简单的代码,我们知道SimpleDateFormat比较费时,所以很多程序里面会定义一个static的SimpleDateFormat,但是奇怪的事情发生了。
package concurrentStudy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* Created by magicalli on 2014/12/13.
*/
public class SimpleDateFormatTest01 {
private static final SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(dateformat.parse("2014-12-19 11:21:21"));
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
模拟多个线程下会用到dateformat这个变量,运行之后发现很多错误,有抛出异常的,有结果错误的,如下图
究其原因,SimpleDateFormat不是线程安全的。只是SimpleDateFormat的doc(据说在JDK6之后才加进去的?)
* Date formats are not synchronized.
* It is recommended to create separate format instances for each thread.
* If multiple threads access a format concurrently, it must be synchronized
* externally.
并发安全,主要由于:
- 多个线程
- 并发访问共享状态
- 并且状态可被修改
要想保证并发安全,上述3个条件,只能满足2个,于是我们有了3种解决方法(C(3, 1) == 3)。
第一种,不要多个线程访问,即加同步或者显示锁:
package concurrentStudy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* Created by magicalli on 2014/12/13.
*/
public class SimpleDateFormatTest01 {
private static final SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final Object lock = new Object();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
System.out.println(dateformat.parse("2014-12-19 11:21:21"));
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
第二种方法,不要共享变量,即“线程封闭(Thread Confinement),又大概有两种,一是栈封闭,即使用局部变量,方法里面自己new一个SimpleDateFormat出来
package concurrentStudy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* Created by magicalli on 2014/12/13.
*/
public class SimpleDateFormatTest03 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
final SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dateformat.parse("2014-12-19 11:21:21"));
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
但是这个有一个坏处,我们一开始说用static的目的是因为new一个SimpleDateFormat比较费时,如果在每次调用的时候new一个出来,感觉有点浪费,于是有了ThreadLocal这个神器。ThreadLocal可以简单理解为一个Map<Thread, T>,即以线程为key的map,这样可以保证每个线程有自己的SimpleDateFormat,避免了各个线程共享。
package concurrentStudy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* Created by magicalli on 2014/12/13.
*/
public class SimpleDateFormatTest04 {
private static final ThreadLocal<SimpleDateFormat> dateformat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
System.out.println("init.......");
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
try {
System.out.println(dateformat.get().parse("2014-12-19 11:21:21"));
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
运行结果可以看到,即使调用了100次,也只针对每个线程new了对象,总共只有10个SimpleDateFormat对象,大大减小了开销。
其实更好的解决方法就是,不用JDK自带的Date等一切日期类!!!用Joda-time,api更友好,更简单易用,功能更强大!
package concurrentStudy;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/**
* Created by magicalli on 2014/12/13.
*/
public class SimpleDateFormatTest05 {
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(dateTimeFormatter.parseDateTime("2014-12-19 11:21:21"));
}
}).start();
}
}
}