2011-2-17 不安全的多线程

本文探讨了SimpleDateFormat在多线程环境下使用的并发问题,并提出了三种解决方案:每次使用时创建新的实例、通过同步方法确保线程安全及根据线程ID分配独立实例。
在我们的代码的里面经常有这样的例子,为了格式化日期(如:2011-02-17), 就需要一个SimpleDateFormat类,同时,为了让整个系统共享这个格式化类,我们把这个格式化类写到一个公共的Util类里面,代码如下:
Util 类:
package com.maneco.art;

import java.text.SimpleDateFormat;

public class Util {
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

}

Util类的使用者:
package com.maneco.art;

import java.text.ParseException;

public class MultiThread implements Runnable {

public static void main(String[] args) {
new Thread(new MultiThread()).start();
new Thread(new MultiThread()).start();
new Thread(new MultiThread()).start();
new Thread(new MultiThread()).start();
}

@Override
public void run() {
try {
for (int i = 0; i < 10000; i++) {
System.out.print(Util.sdf.parse("2000-01-05"));
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}

如上面的代码,就会产生下面的异常:
Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Long.parseLong(Unknown Source)
at java.lang.Long.parseLong(Unknown Source)
at java.text.DigitList.getLong(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at com.maneco.art.MultiThread.run(MultiThread.java:18)
at java.lang.Thread.run(Unknown Source)

原因在于:
如果是单线程,使用这个Util类是没有问题的;
如果是多线程的话,就会产生并发访问同一个实例化的SimpleDateFormat类。
JDK API中声明DateFormat类和SimpleDateFormat类都不是线程同步的,推荐对于每个线程创建一个实例。如果多线程访问的话,自己实现多线程同步;
1: 最简单的方法是在每次使用的时候,new一个新的DateFormat;效率稍许损失,不能达到系统共享同一个格式化类。最简单的编程模型。推荐。
2: 增加线程同步访问: 但是多线程的效率将损失,并且容易导致死锁。
private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
public static synchronized Date formatStr(String str) throws ParseException {
return Util.df.parse(str);
}

下面这种线程同步是没有意义的,因为仅仅拿到reference时是线程同步的,而不是使用时同步的。
private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
public static synchronized DateFormat getDateFomrater() {
return Util.df;
}

3: 如果对性能要求严格,可以封装一个类,在同步的代码里面对于每个线程根据线程Id去返回对应的实例类,
效率稍微损失,不会导致死锁,系统的格式化聚集在一个访问点:
private static final Map<Long, DateFormat> DateFormaterMap = new HashMap<Long, DateFormat>();
// return date formater by thread Id
public static DateFormat getDateFormaterByThread() {
Long tId = Thread.currentThread().getId();

if (Util.DateFormaterMap.containsKey(tId)) {
return Util.DateFormaterMap.get(tId);
} else {
// another strategy: use SimpleDateFormat's clone method with a default one
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Util.DateFormaterMap.put(tId, sdf);
return sdf;
}
}

建议在写单元测试的时候,增加一项多线程测试。
基于模拟退火的计算器 在线运行 访问run.bcjh.xyz。 先展示下效果 https://pan.quark.cn/s/cc95c98c3760 参见此仓库。 使用方法(本地安装包) 前往Releases · hjenryin/BCJH-Metropolis下载最新 ,解压后输入游戏内校验码即可使用。 配置厨具 已在2.0.0弃用。 直接使用白菜菊花代码,保留高级厨具,新手池厨具可变。 更改迭代次数 如有需要,可以更改 中39行的数字来设置迭代次数。 本地编译 如果在windows平台,需要使用MSBuild编译,并将 改为ANSI编码。 如有条件,强烈建议这种本地运行(运行可加速、可多次重复)。 在 下运行 ,是游戏中的白菜菊花校验码。 编译、运行: - 在根目录新建 文件夹并 至build - - 使用 (linux) 或 (windows) 运行。 最后在命令行就可以得到输出结果了! (注意顺序)(得到厨师-技法,表示对应新手池厨具) 注:linux下支持多任务选择 云端编译已在2.0.0弃用。 局限性 已知的问题: - 无法得到最优解! 只能得到一个比较好的解,有助于开阔思路。 - 无法选择菜品数量(默认拉满)。 可能有一定门槛。 (这可能有助于防止这类辅助工具的滥用导致分数膨胀? )(你问我为什么用其他语言写? python一个晚上就写好了,结果因为有涉及json读写很多类型没法推断,jit用了,算这个太慢了,所以就用c++写了) 工作原理 采用两层模拟退火来最大化总能量。 第一层为三个厨师,其能量用第二层模拟退火来估计。 也就是说,这套方法理论上也能算厨神(只要能够在非常快的时间内,算出一个厨神面板的得分),但是加上厨神的食材限制工作量有点大……以后再说吧。 (...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值