线程安全

来源:互联网 发布:知乎 安卓下载 编辑:程序博客网 时间:2024/06/08 01:55

整理自知乎:

https://www.zhihu.com/question/26595480/answer/33533759

https://www.zhihu.com/question/23244293/answer/24032098


       线程安全有不止一种定义,而且互不兼容。

       比较认可的是在《Java concurrency inpractice》一书中的定义:一个不论运行时(Runtime)如何调度线程都不需要调用方提供额外的同步和协调机制还能正确地运行的类是线程安全的。

       一个线程安全的对象应当满足以下三个特点:
       •
无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织(interleaving)。
       •
调用端代码无须额外的同步或其他协调动作。

       • 多个线程同时访问时,其表现出正确的行为。

       依据这个定义,C++ 标准库里的大多数 class 都不是线程安全的,包括 std::string、std::vector、std::map、std::shared_ptr 等等。
       而 C 系统库大多数函数是线程安全的,包括malloc/free/printf/gettimeofday 等等。


       这个定义太过于严格,也可定义为有条件的线程安全类,对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。


       线程不安全就是如果不提供额外的同步和协调机制,进行数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

       多线程的场景很多很复杂,难以穷尽地说那些条件下是或者不是线程安全的,但是有一些常用的肯定线程安全的场景:

      1.无状态的一定是线程安全的。这个很好理解,因为所谓线程不安全也就是一个线程修改了状态,而另一个线程的操作依赖于这个被修改的状态。

      2.只有一个状态,而且这个状态是由一个线程安全的对象维护的,那这个类也是线程安全的。(可以理解为操作具有原子性)比如你在数据结构里只用一个AtomicLong来作为计数器,那递增计数的操作都是线程安全的,不会漏掉任何一次计数,而如果你用普通的long做++操作则不一样,因为++操作本身涉及到取数、递增、赋值三个操作,某个线程可能取到了另外一个线程还没来得及写回的数就会导致上一次写入丢失。

      3.有多个状态的情况下,维持不变性(invariant)的所有可变(mutable)状态都用同一个锁来守护的类是线程安全的。这一段有些拗口,首先类不变性的意思是指这个类在多线程状态下能正确运行的状态,其次用锁守护的意思是所有对该状态的操作都需要获取这个锁,而用同一个锁守护的作用就是所有对这些状态的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。所以这里的关键在于如何确定不变性,可能你的类的某些状态对于类的正确运行是无关紧要的,那就不需要用和其他状态一样的锁来守护。因此我们常可以看到有的类里面会创建一个新的对象作为锁来守护某些和原类本身不变性无关的状态。

      上面这三种只是一种归纳,具体到实际应用时,要看你的类哪些状态是必须用锁来守护的,灵活变通。

我对上述3点总结如下:

1、无状态,即访问对象不会更改。

2、对访问对象的操作具有原子性。

3、对对象资源加锁保护,所有对该对象资源的操作都需要获取这个锁,作用就是所有对这些资源的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。

原创粉丝点击