高并发下的线程安全实现——线程局部变量
来源:互联网 发布:北大方正字体淘宝收费 编辑:程序博客网 时间:2024/06/04 20:12
今天我们来讨论另外一种线程安全的实现方法。如果说互斥同步是多线程间的数据共享,那么线程局部变量就是线程间的数据隔离。ThreadLocal把共享数据的可见范围限制在同一个线程之内,这样无须同步也能实现线程之间数据不争用的问题。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。线程局部变量实现的原理也比较简单:每个线程的Thread对象中都有一个ThreadLocalMap对象,这个map存储了一组以该线程所对应的哈希码ThreadLocal.threadLocalHashCode为键,以线程局部变量为值的K-V值对,每一个线程对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以获取相对应的变量了,这个变量就是上文中提到的线程局部变量的副本。
我们来分析一下java中最常用到的日期时间格式化工具类SimpleDateFormat。SimpleDateFormat类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类都不是线程安全的,以下代码:
public class DateUtil { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date) throws ParseException { return sdf.format(date); } public static Date parse(String strDate) throws ParseException { return sdf.parse(strDate); }}
没有使用任何同步手段来确保线程安全,我们使用一个测试用例来测试一下是否运行正常:
@Test public void test01(){ ExecutorService service = Executors.newCachedThreadPool(); // 创建一个线程池 for (int i = 0; i < 10; i++) { Runnable runnable = new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()+":"+DateUtil.parse("2017-06-24 06:02:20")); Thread.sleep(30000); } catch (Exception e) { System.out.println(e.getMessage()); } } }; service.execute(runnable);// 为线程池添加任务 } }
运行结果为:
pool-1-thread-2:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-7:Sat Jun 24 06:02:02 CST 2017
pool-1-thread-3:Sat Jun 24 06:02:02 CST 2017
pool-1-thread-6:Tue Jul 31 06:02:20 CST 2018
pool-1-thread-8:Tue Jul 24 06:02:20 CST 2018
pool-1-thread-4:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-9:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-5:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-1:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-10:Sat Jun 24 06:02:20 CST 2017
很明显,线程6和线程8输出的时间是有错误的,这是因为SimpleDateFormat和DateFormat类不是线程安全的。在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。以下代码:
public class DateSyncUtil{ private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{ synchronized(sdf){ return sdf.format(date); } } public static Date parse(String strDate) throws ParseException{ synchronized(sdf){ return sdf.parse(strDate); } }}
我们使用一个测试用例来测一下看是否运行正常:
public class SyncTest { @Test public void test01(){ ExecutorService service = Executors.newCachedThreadPool(); // 创建一个线程池 final CountDownLatch cdOrder = new CountDownLatch(1); for (int i = 0; i < 10; i++) { Runnable runnable = new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()+":"+DateSyncUtil.parse("2017-06-24 06:02:20")); cdOrder.await(); } catch (Exception e) { e.printStackTrace(); } } }; service.execute(runnable);// 为线程池添加任务 } }}
运行结果:
pool-1-thread-9:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-3:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-4:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-6:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-1:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-2:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-5:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-10:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-8:Sat Jun 24 06:02:20 CST 2017
通过结果可以看出以上代码肯定是线程安全的,可以保证数据的正确性。但是在高并发环境下,当一个线程调用该方法时,其他想要调用此方法的线程就要阻塞,多线程并发量大的时候会对性能有一定的影响。如果系统对性能有比较高的要求,那么推荐使用ThreadLocal来隔离数据在一个线程中:
public class ConcurrentDateUtil { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); } public static String format(Date date) { return threadLocal.get().format(date); }}
调用上面的测试用例测试的结果如下:
pool-1-thread-4:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-8:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-3:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-1:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-6:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-9:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-2:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-5:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-10:Sat Jun 24 06:02:20 CST 2017
pool-1-thread-7:Sat Jun 24 06:02:20 CST 2017
使用ThreadLocal,也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。
虽然ThreadLocal相比于互斥同步在时间性能上面有一定的优势,但是需要注意它们两者所应用的场景,ThreadLocal用于数据隔离,即当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。而synchronized用于数据同步,即当多个线程需要访问或修改某个对象的时候使用synchronized来阻塞其他线程从而只允许一个线程访问或修改该对象。
- 高并发下的线程安全实现——线程局部变量
- 高并发下的线程安全实现——互斥同步
- ConcurrentHashMap 高并发、高吞吐量的线程安全HashMap实现
- 并发编程--线程局部变量的使用
- 局部变量线程安全的理解
- 高并发下线程安全的单例模式
- 高并发下线程安全的单例模式
- 高并发下线程安全的单例模式
- 高并发下线程安全的单例模式
- 局部变量线程安全测试
- 【多线程高并发】线程安全
- ThreadLocal-单例模式下高并发线程安全
- Java并发编程--线程局部变量使用
- Java并发编程-04-线程局部变量
- Java并发编程规则:原子变量实现线程安全
- 【线程高新】——【ThreadLocal实现线程范围的共享变量】
- 高并发下线程安全的单例模式(最全最经典)
- 高并发下线程安全的单例模式(最全最经典)
- bzoj3597 [Scoi2014]方伯伯运椰子 01分数规划
- Windows下安装redis以及在cakephp内使用redis
- 二叉树的遍历 (二叉树遍历转换)
- Linux常用命令(文字编辑)---tail命令
- log4j.properties配置文件
- 高并发下的线程安全实现——线程局部变量
- E
- Android PendingIntent简略总结
- iOS 朋友圈点赞评论,发布朋友圈,想要的都有
- Leetcode--648. Replace Words
- lintcode -- 整数转罗马数字
- select count(*) 速度慢的原因主要有什么?
- H5基础知识第十二课时(事件)
- C#利用HttpWebRequest 测试网站是否可以正常访问