Java线程安全小结
一、引言
Java开发过程中许多的时候都会涉及到各种各样的并发编程的问题,然而说起并发编程总需要格外的关注线程安全的问题。最近呢一直在基于Jstorm开发日志处理程序,由于Jstorm的特性,多线程随处可见。所以程序中也需要特别关注线程安全的问题。这次项目开发过程也遇到了不少的问题,通过不断的查询资料,不断的修改问题也确实收获了不少的知识。因此写一下最近关于并发编程的学习和总结。
二、多线程基础
在并发编程中,线程和锁起着至关重要的作用,要完成健壮的并发程序就必须要正确的使用线程和锁。在我的实战过程中我觉得有两点需要我们特别的注意。
- 对可变共享资源的操作
- 当前的锁对象
这两个地方不考虑清楚很容易写出线程不安全的代码。后面我会结合相关的示例来说明这一点。
术语
共享资源:能够被多个线程同时访问的资源 竞态条件:当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件 临界区:导致竞态条件发生的代码区
什么是线程安全?
在并发编程中我们最为关心的便是线程安全的问题。只有线程安全的程序在并发编程中才有用武之地。
那么,到底什么是线程安全呢?
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类时线程安全的。
上面这句话给出了线程安全的一个定义,然而不同的实际场景这个正确的行为可能各不相同,需要我们开发去主观的判断。我在实战过程中往往会在每个操作执行过程中去考虑此时共享资源所处的状态,然后不断的测试代码的运行,查看中间过程共享资源的真实状态,从而实现出线程安全的代码。
三、关于线程安全的实战
上面概括的讲了一些基础的知识,接下来纪录下最近工作中遇到的问题。在基础中我强调了两个注意事项,在接下来的示例中我们就能够发现弄清楚代码中对共享资源的操作和当前同步中获得的所对象是什么对写出线程安全的代码的重要性。
字符串常量作为对象锁
再次考虑两个注意点,第一,共享资源是什么;第二当前的对象锁是什么。
上图中的list集合即为共享资源,同步代码块中是对共享的操作。而当前的synchronized获取的锁则为字符串s。由于以上图中创建的字符串直接存储在jvm的常量池中,而且一个jvm中只保存一份。因此每个线程要想访问同步代码块中的代码都需要获得字符串s的锁。所以多个线程竞争同一个锁,从而到达每次只有一个线程可以访问同步代码块中的代码,即线程安全。
这次代码只改动了很小一部分,但是结果却是很出乎意料,现在这段代码编程了线程不安全的了。当时出现这个问题的时候我们也是有点意外。
遇到问题,首先判断哪些地方操作了共享资源list,检查整个代码发现只有图中的同步代码块中的代码。于是判断,共享资源的操作都在同步代码块中,共享资源的操作不是问题的关键。那边应该就是第二个条件对象锁出现了问题。对比图一的代码,图一之所以线程安全是因为所对象只有一个,每个线程去竞争同一个对象锁,从而保证同一时间只有一个线程执行同步代码块中的代码。所以猜测图二中的s对象不唯一,每个线程的锁住的对象各不相同,从而导致同一时间有多个线程执行了同步代码块中的代码。
接下来我们看看图二中字节码反编译后的结果。
图中可以看到编译器对字符串操作符进行了处理,在用+连接两个字符串时编译器最终会通过StringBuilder的append方法拼接,最后通过调用toString方法获取字符串,因此不难理解这里每个线程都会new出自己的StringBuilder对象,所以这里每个线程获取的对象锁都是自己的StringBuilder对象,并没有去竞争同一个对象锁。从而造成了线程不安全的产生。
上面这个示例主要是错误的锁对象的使用造成了线程不安全。
错用this关键字
在使用同步的过程中经常会看到synchronized(this)的写法。此时应该要明白这里this的真正代表的含义。这里this表示当前对象,而synchronized(this)则表示需要需要获取当前对象的锁,才行访问同步代码块中的对象。因此synchronized(this)只适用多线程共用同一个实例。
public class SyncTest implements Runnable { private static final List<String> listA = new ArrayList<>(); private static final List<String> listB = new ArrayList<>(); private static final List<String> listC = new ArrayList<>(); private static final List<String> listD = new ArrayList<>(); @Override public void run() { while (true) { List<String> list = null; TestEnum testEnum = TestEnum.getEnumByTime(); switch (testEnum) { case aaa: list = listA; break; case bbb: list = listB; break; case ccc: list = listC; break; case ddd: list = listD; break; } synchronized (this) { /*在多实例并发访问的时候会出现线程不安全的现象, 因为每个实例竞争的对象锁不是同一个,但是static的变量所有实例共享 */ list.add(Thread.currentThread().getName() + "," + System.currentTimeMillis()); for (String a : list) { System.out.println(a + ",size = " + list.size()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
四、总结
理论知识只有运用在实践过程中才能更加深刻更加透彻的被理解。在线程安全的问题出现时,搞清两个重点去排查问题的手段也非常的实用。并发编程的第一要务便是安全,在安全的基础之上才是性能。总之,最近基于Jstorm开发程序使自己多线程的理解又更加深了一些。在工作中深度学习点滴技术。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java中PO、DO、TO、DTO、 VO、 BO、POJO 、DAO的概念
1.PO(persistant object) 持久对象 在 o/r 映射的时候出现的概念,如果没有 o/r 映射,没有这个概念存在了。通常对应数据模型 ( 数据库 ), 本身还有部分业务逻辑的处理。可以看成是与数据库中的表相映射的 java 对象。最简单的 PO 就是对应数据库中某个表中的一条记录,多个记录可以用 PO 的集合。 PO 中应该不包含任何对数据库的操作。 2.DO(Domain Object)领域对象 就是从现实世界中抽象出来的有形或无形的业务实体。 3.TO(Transfer Object) ,数据传输对象 不同的应用程序之间传输的对象 4.DTO(Data Transfer Object)数据传输对象 这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。 5.VO(value object) 值对象 通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出的业务对象 , 可以和表对...
- 下一篇
Java主流日志工具介绍和使用
概述 本文的目的并不是详细介绍日志使用,而在于对现有主流日志系统的一个大致介绍,其目的是让我们更加合理的去使用日志,管理日志依赖关系。因为在开发过程中,我发现应用下面关于log的jar包非常的混乱,而这种混乱常常会带来jar包冲突、多份日志输出等问题,造成不必要的麻烦。比如你应用采用了log4j作为日志实现,但是你又通过间接依赖的方式引入了logback的包,这样开发者往往很难察觉,往往是出现了相应的异常现象才排查出log冲突的问题。 本文会先介绍现在主流日志框架,然后提出一个一些应用日志的规范,如果可行度很高,后续会给出更为严格的检测机制,帮助开发者去发现问题,防范于未然。 日志框架的历史 在Java开发过程中可用的日志工具非常之多,比如: Apache Commons Logging(Jakarta Commons Logging,JCL) Simple Logging Facade for Java (SLF4J) Apache Log4j(Log4j2) Java Logging API(JUL) Logback tinylog 在这些日志组件当中,最早得到广泛应用的是log4...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装Docker,最新的服务器搭配容器使用
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7设置SWAP分区,小内存服务器的救世主