`

线程安全性

阅读更多
1、什么是线程安全性

1.1 不可用状态

调用一个函数(假设该函数是正确的)操作某对象常常会使该对象暂时陷入不可用的状态(通常称为不稳定状态),等到操作完全结束,该对象才会重新回到完全可用的状态。

1.2 线程安全性的核心问题

如果其他线程企图访问一个处于不可用状态的对象,该对象将不能正确响应从而产生无法预料的结果,如何避免这种情况发生是线程安全性的核心问题。

单线程的程序中是不存在这种问题的,除非有异常发生。

1.3 线程安全的定义

给线程安全下定义比较困难。存在很多种定义,如:“一个类在可以被多个线程安全调用时就是线程安全的”。

实际上,所有线程安全的定义都有某种程序的循环,因为它必须符合类的规格说明 ——这是对类的功能、其副作用、哪些状态是有效和无效的、不可变量、前置条件、后置条件等等的一种非正式的松散描述。

类要成为线程安全的,首先必须在单线程环境中有正确的行为。

正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

我们都知道,Vector的所有方法都是同步的,然而,尽管如此,在多线程环境下有些时候不进行额外的同步仍然是不安全的。

考虑下面代码:
    Vector v = new Vector();
    // contains race conditions -- may require external synchronization
    for (int i=0; i<v.size(); i++) {
      doSomething(v.get(i));
    }

如果另一个线程恰好在错误的时间里删除了一个元素,则get()会抛出一个ArrayIndexOutOfBoundsException。

这里发生的事情是:get(index)的规格说明里有一条前置条件要求index必须是非负的并且小于size()。但是,在多线程环境中,没有办法可以知道上一次查到的size()值是否仍然有效,因而不能确定i<size(),除非在上一次调用了size()后独占地锁定Vector。

更明确地说,这一问题是由 get() 的前置条件是以 size() 的结果来定义的这一事实所带来的。只要看到这种必须使用一种方法的结果作为另一种讲法的输入条件的样式,它就是一个状态依赖,就必须保证至少在调用这两种方法期间元素的状态没有改变。一般来说,做到这一点的唯一方法在调用第一个方法之前是独占性地锁定对象,一直到调用了后一种方法以后。

2、Java类的线程安全级别

Bloch给出的描述五类线程安全性的分类方法。

2.1 不可变

不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java  类库中大多数基本数值类如 Integer、String和 BigInteger都是不可变的。

2.2 线程安全

由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的——许多类,如Hashtable或者Vector都不能满足这种严格的定义。

2.3 有条件的线程安全

有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。最常见的例子是遍历由Hashtable或者Vector或者返回的迭代器——由这些类返回的fail-fast迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的——并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。

2.4 线程兼容

线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个synchronized块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像Collections.synchronizedList()一样)。也可能意味着用synchronized块包围某些操作序列。

常见类:ArrayList、HashMap、SimpleDateFormat、Connection和ResultSet等。

2.5 线程对立

线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。

3、记录线程安全级别的好处

3.1 记录线程安全

通过将类记录为线程安全的(假设它确实是线程安全的),您就提供了两种有价值的服务:您告知类的维护者不要进行会影响其线程安全性的修改或者扩展,您还告知类的用户使用它时可以不使用外部同步。

3.2 记录有条件线程安全或线程兼容

通过将类记录为线程兼容或者有条件线程安全的,您就告知了用户这个类可以通过正确使用同步而安全地在多线程中使用。

3.3 线程对立

通过将类记录为线程对立的,您就告知用户即使使用了外部同步,他们也不能在多线程中安全地使用这个类。

知道了线程安全级别,使用时就可以很好的预防严重问题的出现。

注意:一个类的线程安全行为是其规格说明中的固有部分,应该成为其文档的一部分。因为还没有描述类的线程安全行为的声明式方式,所以必须用文字描述。

4、Servlet的线程安全性

Servlet/JSP 默认是以多线程模式执行的。Servlet 体系结构是建立在 Java 多线程机制之上的,它的生命周期是由 Web 容器负责的。当客户端第一次请求某个 Servlet 时,Servlet  容器将会根据 web.xml 配置文件实例化这个Servlet 类。当有新的客户端请求该 Servlet 时,一般不会再实例化该 Servlet 类,也就是有多个线程在使用这个实例。Servlet 容器会自动使用线程池等技术来支持系统的运行。这样,当两个或多个线程同时访问同一个 Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。

4.1 无状态Servlet

当Servlet不包含域(成员变量),也没有引用其他类的域,使用的只是局部变量,而局部变量是保存在线程栈中的(各个线程有自己一份)。因而无状态Servlet是线程安全的。

4.2 有状态Servlet

书中举了一个例子,接收两个参数(request中的),计算和(result)。无状态时,result是局部变量,现在提升为实例变量。这样,多用户访问时,有可能就会出现自己的结果显示在别人浏览器中的情况。

解决这种线程不安全性,其中一个主要的方法就是取消 Servlet的实例变量,变成无状态的Servlet;另外一种方法是对共享数据进行同步操作。使用synchronized关键字能保证一次只有一个线程可以访问被保护的区段。

线程安全问题主要是由实例变量造成的,因此在 Servlet 中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码。

5、补充:Struts1.x与Struts2的线程安全性

5.1 Struts1.x的线程安全性

经过对struts1.x源码的研读发现:
struts1.x获取action的方式是单例的,所有的action都被维护在一个hashMap里,当有请求到达时,先根据action的名称去hashMap里查找要请求的Action是否已经存在,如果存在,则直接返回hashMap里的action。如果不存在,则创建一个新的Action实例。这与Servelt是类似的。

因而,Action类中不应该声明带有状态的实例变量(与Servlet类似),而应该使用ActionForm,因为ActionForm是通过参数形式传入action的,不存在共享变量的问题,其实每个request产生的ActionForm实例也是不同的。

在Struts1.x与Spring集成时,配置Action的Bean时,scope可以不配,因为默认为“singleton”。经过Polaris测试发现,尽管Struts1.x内部对Action实例的产生是“单例模式”,然而,如果将其交由Spring管理,其实例数量是由Spring的scope决定的,否则Action 则是由整合插件中的StrutsSpringObjectFactory来创建且仍然是单例的, 可以通过在Action中打印this来测试scope为singleton与prototype时的不同:singleton时,只产生一个实例;为prototype时,每个请求产生产生一个实例。集成的时候,建议Action的Bean不配scope或配成singleton,以利用Struts1自身提供的线程模式,以获得最大性能或资源利用率。

Spring中singleton与prototype的不同:

当spring容器中管理bean属性为singleton时,spring容器会管理该bean整个生命周期;当bean的作用域为prototype时,每次调用到该bean都相当于重新new了一次,new出来的对象 如果没有引用,就会被JVM垃圾回收机制回收的。虽然都说spring是容器,的确没错,但是这人为了形象的描述它能带来的功能,其实它的管理不管理生命周期,其实就看它保存没保存这个对象的引用,虽然singleton是spring管理的,但它在spring容器结束的时候,spring也就是让这个引用指向一个空对象而已。

5.2 Struts2的线程安全性

Struts 2 的 Action 对象为每一个请求产生一个实例,因此,虽然在Action中定义了很多全局变量,也不存在线程安全问题。

Struts 2框架在处理每一个用户请求的时候,都建立一个单独的线程进行处理,值栈ValueStack也是伴随着局部线程而存在的。在该线程存在过程中,可以随意访问值栈,这就保证了值栈的安全性。

在Struts 2中,ActionContext(数据环境)是一个局部线程,这就意味着每个线程中的ActionContext内容都是唯一的。所以开发者不用担心Action的线程安全。

在Struts2与Spring集成时,配置Action的Bean时一定记得加上scope属性,值为:prototype,否则会有线程安全问题。

5.3 Struts1.x与Struts2的性能问题

Struts1.x的单例策略造成了一定的限制,开发时要注意线程安全性问题。

Struts2是线程安全的,据说,Servlet容器会给每一个请求产生许多丟弃的对象,并且不会导致性能和垃圾回收问题。Polaris没有测试,有兴趣的您可以试试。不过,Polaris认为Apache放弃Struts1的更新,转向Struts2,性能方面应该不会比Struts1差。
分享到:
评论

相关推荐

    C#多线程List的非线程安全性

    总之,理解并处理多线程环境下的线程安全问题是提升C#应用程序稳定性和性能的关键。在使用List或其他非线程安全的数据结构时,要时刻警惕潜在的并发问题,并采取适当的同步措施,确保数据的一致性和完整性。

    验证QList线程安全性的小程序

    申明:不是原创,不是原创,只是转载。 这是一个来自网上的例子 ...用于测试QList的线程安全性,因原作者只给出源代码,没有给出测试结果,这里生成一个QT工程,打开即可编译,内部有ReadME.txt,简要说明

    java多线程安全性基础介绍.pptx

    java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...

    Java并发中的线程安全性

    ### Java并发中的线程安全性 #### 1. 引言 随着Java技术的发展以及多核处理器的普及,Java并发编程成为软件开发中的一个重要领域。Java并发控制问题是国内外学者研究的热点之一,特别是在J2SE 1.5版本中引入了`...

    java线程安全性总结

    用思维导图将Java线程安全性相关基本概念联系起来

    深入研究Servlet线程安全性问题.pdf

    ### 深入研究Servlet线程安全性问题 #### 一、引言 Servlet技术作为Java Web开发中的核心组件之一,因其高效性和灵活性被广泛应用于Web应用程序的开发中。Servlet能够处理HTTP请求,并产生相应的响应。它的一个...

    Java多线程编程的线程安全性.docx

    Java多线程编程的线程安全性是开发过程中必须关注的重要概念。线程安全指的是一个类在多线程环境中能够正确地处理并发访问,不会因为线程间的交互导致数据的不一致或异常行为。线程安全性的核心问题主要包括原子性、...

    Java理论与实践:描绘线程安全性

    Java中的线程安全性是并发编程中的关键概念,它关乎到多线程环境下程序的稳定性和正确性。线程安全的类意味着在多个线程并行访问时,它们的行为仍然是正确和一致的,无需额外的同步措施。然而,线程安全并不简单地...

    深入研究Servlet线程安全性问题

    深入研究Servlet线程安全性问题...

    java线程安全性精讲.docx

    Java线程安全性是多线程编程中的核心概念,关乎程序的稳定性和正确性。Java提供了多种机制来确保线程安全,主要包括原子性、可见性和有序性。 **原子性**是线程安全的基础,保证了操作不会被其他线程打断。Java提供...

    Spring并发访问的线程安全性问题.docx

    由于多个线程可能同时访问并修改这些变量,数据的完整性可能会受到破坏,这就是所谓的“线程不安全”或“并发访问的线程安全性问题”。 例如,假设在Controller中有以下代码: ```java @Controller public class ...

    C#线程安全的事件类研究报告

    然而,仅仅确保事件处理程序列表的线程安全性还不够。在触发事件时,你也需要考虑线程安全。一个常见的做法是使用`lock`关键字或`Monitor`类来保护事件触发过程: ```csharp private object _eventLock = new ...

    servlet线程安全问题

    Servlet 线程安全问题是指在使用 Servlet 编程时,如果不注意多线程安全性问题,可能会导致难以发现的错误。Servlet/JSP 技术由于其多线程运行而具有很高的执行效率,但这也意味着需要非常细致地考虑多线程的安全性...

    shared_ptr线程安全性全面分析

    《shared_ptr线程安全性全面分析》 在C++的智能指针家族中,`shared_ptr`是一个重要成员,它提供了一种自动管理内存的方式,尤其是当多个指针共享同一对象时。`shared_ptr`的设计考虑到了多线程环境中的线程安全性...

    《java并发编程实战》读书笔记-第2章-线程安全性

    《java并发编程实战》读书笔记-第2章-线程安全性,脑图形式,使用xmind8制作 包括引言、线程安全性定义、原子性、加锁机制、使用锁保护状态、活跃性与性能等内容

    C# 单例模式详解与线程安全性实现

    内容概要:本文详尽地阐述了 C# 中单例模式的设计思想以及其实现方式,并且特别针对单例模式的线例...方法来代替lock达到更加简明的线程安全代码设计也可以自行进一步深入探索和研究,提高代码效率的同时保证安全性。

    shushu1234#articles-backup#2018-08-16-JCP-线程安全性1

    更多关于 Synchronized 修饰的查看:Java并发-线程安全性每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁(Monitor

    局部变量线程安全测试

    这些测试可能包括并发读写、竞争条件、死锁等问题的测试,通过运行这些测试并分析结果,我们可以理解在不同情况下局部变量是否能够保持其线程安全性。 总之,局部变量线程安全测试是一项重要的软件质量保证措施,...

Global site tag (gtag.js) - Google Analytics