对于从事后端开发的同学来说,并发编程肯定再熟悉不过了 。
说实话,在JAVA中并发编程是一大难点,至少我是这么认为的 。不光理解起来比较费劲,使用起来更容易踩坑 。
不信,让继续往下面看 。
今天重点跟大家一起聊聊并发编程的10个坑,希望对你有帮助 。
文章插图
1. SimpleDateFormat线程不安全在java8之前,我们对时间的格式化处理,一般都是用的SimpleDateFormat类实现的 。例如:
@Servicepublic class SimpleDateFormatService {public Date time(String time) throws ParseException {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return dateFormat.parse(time);}}
如果你真的这样写,是没问题的 。就怕哪天抽风,你觉得dateFormat是一段固定的代码,应该要把它抽取成常量 。
于是把代码改成下面的这样:
@Servicepublic class SimpleDateFormatService {private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public Date time(String time) throws ParseException {return dateFormat.parse(time);}}
dateFormat对象被定义成了静态常量,这样就能被所有对象共用 。如果只有一个线程调用time方法,也不会出现问题 。
但Serivce类的方法,往往是被Controller类调用的,而Controller类的接口方法,则会被Tomcat的线程池调用 。换句话说,可能会出现多个线程调用同一个Controller类的同一个方法,也就是会出现多个线程会同时调用time方法的情况 。
而time方法会调用SimpleDateFormat类的parse方法:
@Overridepublic Date parse(String text, ParsePosition pos) {...Date parsedDate;try {parsedDate = calb.establish(calendar).getTime();...} catch (IllegalArgumentException e) {pos.errorIndex = start;pos.index = oldStart;return null;}return parsedDate;}
该方法会调用establish方法:Calendar establish(Calendar cal) {...//1.清空数据cal.clear();//2.设置时间cal.set(...);//3.返回return cal;}
其中的步骤1、2、3是非原子操作 。但如果cal对象是局部变量还好,坏就坏在parse方法调用establish方法时,传入的calendar是SimpleDateFormat类的父类DateFormat的成员变量:
public abstract class DateFormat extends Forma {....protected Calendar calendar;...}
这样就可能会出现多个线程,同时修改同一个对象即:dateFormat,他的同一个成员变量即:Calendar值的情况 。这样可能会出现,某个线程设置好了时间,又被其他的线程修改了,从而出现时间错误的情况 。
那么,如何解决这个问题呢?
- SimpleDateFormat类的对象不要定义成静态的,可以改成方法的局部变量 。
- 使用ThreadLocal保存SimpleDateFormat类的数据 。
- 使用java8的DateTimeFormatter类 。
我们都知道,单例模式有:饿汉模式和懒汉模式两种 。
饿汉模式代码如下:
public class SimpleSingleton {//持有自己类的引用private static final SimpleSingleton INSTANCE = new SimpleSingleton();//私有的构造方法private SimpleSingleton() {}//对外提供获取实例的静态方法public static SimpleSingleton getInstance() {return INSTANCE;}}
使用饿汉模式的好处是:没有线程安全的问题,但带来的坏处也很明显 。private static final SimpleSingleton INSTANCE = new SimpleSingleton();
一开始就实例化对象了,如果实例化过程非常耗时,并且最后这个对象没有被使用,不是白白造成资源浪费吗?还真是啊 。
这个时候你也许会想到,不用提前实例化对象,在真正使用的时候再实例化不就可以了?
这就是我接下来要介绍的:懒汉模式 。
具体代码如下:
public class SimpleSingleton2 {private static SimpleSingleton2 INSTANCE;private SimpleSingleton2() {}public static SimpleSingleton2 getInstance() {if (INSTANCE == null) {INSTANCE = new SimpleSingleton2();}return INSTANCE;}}
示例中的INSTANCE对象一开始是空的,在调用getInstance方法才会真正实例化 。嗯,不错不错 。但这段代码还是有问题 。
假如有多个线程中都调用了getInstance方法,那么都走到 if (INSTANCE == null) 判断时,可能同时成立,因为INSTANCE初始化时默认值是null 。这样会导致多个线程中同时创建INSTANCE对象,即INSTANCE对象被创建了多次,违背了只创建一个INSTANCE对象的初衷 。
推荐阅读
- 聊聊 HTTP/2 的多路复用
- 糖尿病|肥胖可能引起糖尿病等50多种并发症!老人也能减肥缓解慢性病
- 聊聊Mybatis的binding模块
- 聊聊|炉石传说:是时候来聊聊这版本最脏职业到底是谁了
- 大学生|传奇世界:聊聊三职业终极技能,每一个都是经典!(上)
- 一个程序小BUG产生的原因
- 今日全民健身日,聊聊你不知道的健身锻炼冷知识
- 从三点进行分析说明,零基础看完也能明白 学习编程有什么用
- 创始人是王思聪,聊聊ig那些事 ig战队是王思聪的吗?
- 月经量少有哪些并发症?月经量少的患者应避免饮食