• 逐步理解Java中的线程安全问题


    什么是Java的线程安全问题?

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

    线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

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

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

    什么是脏数据?

    先百度:脏数据

    简单的说:

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

    直观的感觉有点像冲突。

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

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

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

    黑人问号。。。

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

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

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

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

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

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

    如何解决线程安全问题?

    对症下药:

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

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

    这里举两个碰到的例子:

    • 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快?源码告诉我们,后者有同步。

  • 相关阅读:
    zoj 3279 线段树 OR 树状数组
    fzu 1962 树状数组 OR 线段树
    hdu 5057 块状链表
    hdu3487 Play with Chain
    bzoj 1588营业额统计(HNOI 2002)
    poj2823 Sliding Window
    poj2828 Buy Tickets
    poj2395 Out of Hay
    poj3667 Hotel
    poj1703 Lost Cows
  • 原文地址:https://www.cnblogs.com/andy1202go/p/8670447.html
Copyright © 2020-2023  润新知