`

请您先登录,才能继续操作

《Java并发编程实践》学习笔记之二:线程安全性(thread-safe)

    博客分类:
  • Java
阅读更多

 

《Java并发编程实践》学习笔记之二:线程安全性(thread-safe)


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中打印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差。

想了解更多、更详细的Struts1.x与Struts2的区别,请访问:http://tech.ddvip.com/2008-12/122852732297849.html

 

1
0
分享到:
评论

相关推荐

    java并发编程实践pdf笔记

    Java并发编程实践是Java开发中不可或缺的一个领域,它涉及到如何高效、正确地处理多线程环境中的任务。这本书的读书笔记涵盖了多个关键知识点,旨在帮助读者深入理解Java并发编程的核心概念。 1. **线程和进程的...

    java并发编程实践笔记

    ### Java并发编程实践笔记知识点详解 #### 一、保证线程安全的方法 1. **不要跨线程访问共享变量:** 当多个线程共享某个变量时,若其中一个线程修改了该变量,其他线程若没有正确同步,则可能读取到错误的数据。...

    JAVA并发编程实践.pdf

    根据提供的信息,“JAVA并发编程实践.pdf”这一文档主要聚焦于Java并发编程的实践与应用,这对于希望深入了解并行处理和多线程技术的开发者来说是非常有价值的资源。下面将基于标题和描述中的关键词“JAVA并发编程...

    Java多线程编程总结

    Java线程:概念与原理 Java线程:创建与启动 Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠 Java线程:线程的调度-...

    Java并发编程学习笔记.rar

    这本"Java并发编程学习笔记"可能是作者在深入研究Java并发特性、工具和最佳实践过程中积累的心得体会。下面,我们将根据这个主题,探讨一些关键的Java并发编程知识点。 1. **线程与进程**:在多任务环境中,线程是...

    JAVA并发编程实践

    根据给定文件的信息“JAVA并发编程实践”以及其描述为“Java并发学习资料”,我们可以从中提炼出关于Java并发编程的一些核心知识点。Java并发编程是Java高级特性之一,它允许开发者编写能够同时执行多个任务的程序,...

    java线程与并发编程实践

    Java线程与并发编程实践是Java开发者必备的技能之一,特别是在多核处理器和高并发应用环境中,有效地管理和利用线程能极大地提升程序的性能。本书《java线程与并发实践编程》由Jeff Friesen撰写,2017年2月出版,...

    JAVA并发编程实践.pdf+高清版+目录 书籍源码

    《JAVA并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。它涵盖了Java并发的核心概念、工具和最佳实践,旨在帮助...通过学习这本书,你将能够有效地应对并发编程中的挑战,提升你的多线程编程能力。

    JAVA并发编程实践-线程安全-学习笔记

    在Java并发编程中,线程安全是一个至关重要的概念,它涉及到多线程环境下对共享数据的正确管理和访问。线程安全意味着当多个线程同时访问一个对象或数据时,对象的状态能够保持一致性和完整性,不会因为并发导致数据...

    Java并发编程学习笔记

    在Java并发编程中,多线程编程是指同时运行多个线程(Thread),每个线程可以执行不同的任务,或者协同完成同一项任务的不同部分。由于线程共享同一进程的内存空间,因此在多线程编程中特别需要关注线程安全(Thread...

    Java并发编程实践-电子书1-9章pdf

    《Java并发编程实践》是Java开发者深入理解并发编程的重要参考资料,尤其对于想要提升多线程应用设计和性能优化技能的程序员来说,这本书提供了丰富的实践经验和深入的理论知识。以下是根据提供的章节内容概述的一些...

    java并发编程实践

    ### Java并发编程实践知识点详解 #### 一、Java并发编程基础 ##### 1.1 并发与并行概念区分 在Java并发编程实践中,首先需要理解“并发”与“并行”的区别。“并发”指的是多个任务同时进行,但实际上可能是在多...

    java并发编程与实践

    4. **并发集合**:Java并发包提供了线程安全的集合实现,如ConcurrentHashMap、CopyOnWriteArrayList和BlockingQueue等。这些集合在多线程环境中能保证数据一致性。 5. **死锁、活锁和饥饿**:文档可能会介绍这些...

    Java并发编程实践

    《Java并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。并发编程是现代软件开发中的核心技能之一,尤其是在多核处理器普及后,利用并发能够极大地提升程序的执行效率。以下将根据提供的章节标题,...

    Java并发编程实践.pdf

    ### Java并发编程实践 #### 一、并发编程基础 ##### 1.1 并发与并行的区别 在Java并发编程中,首先需要理解“并发”(Concurrency)和“并行”(Parallelism)的区别。“并发”指的是多个任务在同一时间段内交替...

Global site tag (gtag.js) - Google Analytics