龙空技术网

java大鸟告诉你正确辨别线程安全与不安全的正确姿势

IT大派 291

前言:

当前你们对“java什么叫线程安全”都比较关切,咱们都想要了解一些“java什么叫线程安全”的相关知识。那么小编在网上搜集了一些对于“java什么叫线程安全””的相关内容,希望朋友们能喜欢,姐妹们一起来了解一下吧!

在学习Java的时候经常会发现有很多名称相似的类,比如HashMap和Hashtable,StringBuffer和StringBuilder等等,他们的名称看起来一样而且功能也差不多一样,所以对于刚刚学习java的同学会很疑惑,他们都是有哪些不同点呢?而在深入研究这个问题的时候,就会发现他们都有这样两个概念,线程安全和线程不安全,这就是本文章主要讲的内容。

1.那么什么是线程安全和线程不安全呢?

假设A和B同时去不同ATM上取同一张卡的1000块钱,如果是线程不安全,那么A和B可以同时取到1000块钱(两人赚大发啦),而如果线程安全呢,就只有一个人能取出来1000块钱。

线程安全是指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性。

线程不安全就是不提供加锁机制保护,有可能出现多个线程先后不同时间更改数据造成所得到的数据是脏数据

2.什么决定了线程的安全问题呢?

线程安全问题都是由程序中的全局变量和静态变量所引起的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

3.有哪些可以解决多线程并发访问资源的安全问题呢?

有三种方式:分别是 同步代码块 、同步方法和锁机制(Lock)

以上两种该方法都用到了Java语言的关键字synchronized,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读/写完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

那么,理解下上面这段话,再抛出新的问题:

什么是脏数据?什么情况会触发线程安全问题?为什么静态变量和线程安全结合紧密?如何解决线程安全问题?如何预防线程安全问题?

什么是脏数据?

先百度:脏数据;

简单的说:

通俗的讲,当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

直观的感觉有点像冲突。

什么情况会触发线程安全问题?为什么静态变量和线程安全结合紧密?

去网上找答案和分析,很多情况会看到这么一句话:

静态变量类型设置的不合理会造成线程不安全。

黑人问号。。。

我们来看下造成线程不安全的几个要素:

多线程应用访问同一块/个数据;

多线程应用:一般来说基本上都是了;

访问同一块数据:这篇文章写的很好,转来分享:在多线程中使用静态方法是否有线程安全问题

总而言之就是这样子的:——》调用静态方法——》调用静态变量——》线程不安全

所以,直接引起线程不安全的是不安全的静态变量,前面并不重要;

如何解决线程安全问题?

对症下药:

不安全的变量——》安全的变量;静态——》动态(每次使用时生成)

后一个方法可以说是设计或者业务上的问题了,需要注意的是第一个,也就是哪些是线程安全,哪些线程不安全,还经常被用作静态变量。

这里举两个碰到的例子:

SimpleDateFormat,不安全;StringBuilder,不安全,StringBuffer,安全;

另外,也可以对大量代码进行同步操作,但不是很推荐:

1、同步方法给多线程访问的成员方法加上synchronized修饰符

public void synchronized doWork(){ // TODO}
使用synchronized修饰的方法,就叫做同步方法,保证线程执行该方法的时候,其他线程只能在方法外等着。2、同步代码块
synchronized(同步锁对象){ // 需要同步操作的代码}
实际上,对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,谁拿到锁,谁就可以进入代码块,其他线程只能在代码块外面等着,而且注意,在任何时候,Java虚拟机最多允许一个线程拥有该同步锁。Java程序运行可以使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。实际上,同步方法和同步代码块差不了多少,在本质上是一样的,两者都用了一个关键字synchronized,synchronized保证了多线程并发访问时的同步操作,避免线程的安全性问题,但是有一个弊端,就是使用synchronized的方法/代码块的性能比不用要低一些,因此如果要用synchronized,建议尽量减小synchronized的作用域。

如何预防线程安全问题?

其实这里想说的是是否需要在开发时对线程安全问题重点考虑。

为什么这么说呢?

比如StringBuilder和StringBuffer,在相应的API文档中有这样的描述:

将StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用StringBuffer。”,提到StringBuffer时,说到“StringBuffer是线程安全的可变字符序列,一个类似于String的字符串缓冲区,虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致”。StringBuilder是一个可变的字符序列,此类提供一个与StringBuffe兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。将StringBuilder的实例用于多个线程是不安全的,如果需要这样的同步,则建议使用StringBuffer。

也就是说,一般推荐使用StringBuilder,这个不安全的家伙!!!

因为它快!!!

这里其实会有两个明显的疑问:

快多少?会有静态的StringBuilder么?

快多少?看这个:String、StringBuffer、StringBuilder的区别与效率比较

结论是量级小看不出,量级大还是有区别。

会有静态的StringBuilder么?我没想到...

所以,线程安全并不是说开发中一直提心吊胆考虑的问题;

简单来说,有静态变量了,多思考下应用场景,查一下前辈踩过的坑,问题基本避免了。

至于衍生问题:为什么StringBuilder比StringBuffer快?源码告诉我们,后者有同步。

标签: #java什么叫线程安全 #多线程调用静态方法安全吗 #java静态变量线程安全吗