线程安全和线程不安全理解 ╰半夏微凉° 2022-08-27 14:40 165阅读 0赞 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据 ================================================================= 概念: 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和 [单线程][Link 1]运行的结果是一样的,而且其他的 [变量][Link 2]的值也和预期的是一样的,就是线程安全的。 或者说:一个类或者程序所提供的接口对于线程来说是 [原子操作][Link 3]或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。 线程安全问题都是由 [全局变量][Link 4]及 [静态变量][Link 5]引起的。 若每个线程中对 [全局变量][Link 4]、 [静态变量][Link 5]只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑 [线程同步][Link 6],否则的话就可能影响线程安全。 安全性: 比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items\[Size\] 的位置存放此元素;2. 增大 Size 的值。 在 [单线程][Link 1]运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1; 而如果是在 [多线程][Link 7]情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。 安全性: [线程安全性][Link 8]不是一个非真即假的命题。 Vector 的方法都是同步的,并且 Vector 明确地设计为在 [多线程][Link 7]环境中工作。但是它的 [线程安全性][Link 8]是有限制的,即在某些方法之间有状态依赖(类似地,如果在迭代过程中 Vector 被其他线程修改,那么由 Vector.iterator() 返回的 iterator会抛出ConcurrentModifiicationException)。 对于 Java 类中常见的 [线程安全性][Link 8]级别,没有一种 [分类系统][Link 9]可被广泛接受,不过重要的是在编写类时尽量记录下它们的线程安全行为。 Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 -- 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 -- 但是这套系统是一个很好的起点。这种 [分类系统][Link 9]的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)。下面几节分别描述了 [线程安全性][Link 8]的这五种类别。 ### 不可变 ### 不可变的对象一定是线程安全的,并且永远也不需要额外的同步 \[1\] 。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。 需要注意的是,对于Integer,该类不提供add方法,加法是使用+来直接操作。而+操作是不具线程安全的。这是提供原子操作类AtomicInteger的原。 线程安全 线程安全的对象具有在上面“线程安全”一节中描述的属性 -- 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排线程都不需要任何额外的同步。这种 [线程安全性][Link 8]保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。 ### 有条件的 ### 有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的 [迭代器][Link 10] \-- 由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的 -- 并且类的文档应该说明是哪个锁(通常是对象的内部 [监视器][Link 11](intrinsic monitor))。 如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。 线程兼容 线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为 [变量][Link 2]实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。 许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。 ### 线程对立 ### 线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。 [Link 1]: http://baike.baidu.com/view/2554947.htm [Link 2]: http://baike.baidu.com/view/296689.htm [Link 3]: http://baike.baidu.com/view/809659.htm [Link 4]: http://baike.baidu.com/view/261041.htm [Link 5]: http://baike.baidu.com/view/675642.htm [Link 6]: http://baike.baidu.com/view/6501899.htm [Link 7]: http://baike.baidu.com/view/65706.htm [Link 8]: http://baike.baidu.com/view/1302402.htm [Link 9]: http://baike.baidu.com/view/509073.htm [Link 10]: http://baike.baidu.com/view/925158.htm [Link 11]: http://baike.baidu.com/view/45702.htm
还没有评论,来说两句吧...