`
足至迹留
  • 浏览: 496896 次
  • 性别: Icon_minigender_1
  • 来自: OnePiece
社区版块
存档分类
最新评论

<基础-2> 构建线程安全应用程序

阅读更多
上一篇主要介绍了程序,进程,线程的概念和区别,进程的组成。然后是线程的创建和基本控制。接着本篇就介绍下什么是线程安全,怎样去保证线程安全的基本方法。

二、 构建线程安全应用程序
2.1 什么是线程安全性
线程安全很难给出一个准确的定义。大都是从不同的方面进行一个描述。当对一个复杂对象进行某种操作时,从操作开始到操作结束,该对象中间肯定会经历若干个非法的中间状态。能保证多线程在使用该对象时,每个开始和结束都是稳定合法状态,中间状态不会被其他线程访问,则是线程安全的。
1) 类要成为线程安全的,则首先必须在单线程环境有正确的行为。
2) 正确性和安全性的关系非常类似事务(ACID)的一致性和独立性之间的关系。

Bloch给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。
1.不可变
不可变的对象一定是线程安全的,如String。
2.线程安全
访问类时不需要做任何同步的类是线程安全的。这个条件很苛刻,HashTable或Vector都不满足这种严格的定义。
考虑下面的代码片段,它迭代一个 Vector 中的元素。尽管 Vector 的所有方法都是同步的,但是在多线程的环境中不做额外的同步就使用这段代码仍然是不安全的,因为如果另一个线程恰好在错误的时间里删除了一个元素,则 get() 会抛出一个 ArrayIndexOutOfBoundsException 。
    Vector v = new Vector();
    // contains race conditions -- may require external synchronization
    for (int i=0; i<v.size(); i++) {
      doSomething(v.get(i));
    }

3.有条件的线程安全
有条件的线程安全类对于单独的操作可以是线程安全的,但某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历Hashtable或Vector.
4.线程兼容
线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全使用。这可能意味着用一个synchronized块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的。许多常见的类都是线程兼容的,如ArrayList,HashMap等。
5.线程对立
是那些不管是否调用了外部同步都不能在并发使用时安全的呈现的类。线程对立很少见,其中一个例子是调用System.setOut()的类。

2.1 servlet的线程安全性
Servlet/jsp默认是以多线程模式执行的,所以在编写代码时需要非常细致地考虑多线程的安全性问题。
Servlet体系结构是建立在java多线程机制之上的,它的生命周期是由web容器负责的。当客户端第一次请求某个servlet时,servlet容器会根据web.xml实例化这个servlet类,当有新的客户端请求该servlet时,一般不会再实例化该类(这个也要看web容器里的配置),也就是多个线程在使用这个实例。

2.3 同步和互斥
线程通信主要通过共享访问字段完成,通常有可能出现两种错误:线程干扰和内存一致性错误。用来防止这些错误的工具是同步(synchronization)。
当两个线程需要使用同一个对象时,存在交叉操作而破坏数据的可能性。这种潜在的干扰在术语上称作:临界区(critical section)。通过同步对临界区的访问可以避免这种线程干扰。
注:
这里有关于临界区,互斥量,信号量的相关概念介绍:
http://blog.csdn.net/bao_qibiao/article/details/4516196

同步是围绕被称为内在锁(intrinsic lock)或者监视器锁(monitor lock)的内部实体构建的,强制对对象状态的独占访问,以及建立可见性所需的发生前关系。
每个对象都具有与其关联的内在锁,按照约定,需要对对象的字段进行独占和一致性访问的线程在访问之前,必须获得这个对象的内在锁,访问完成之后必须释放内在锁。从获得锁到释放锁的时间段内,线程被称为拥有内在锁。只要有线程拥有内在锁,其他线程就不能获得同一个锁,试图获得锁的其他线程将被阻塞。注意一定是试图获取同一个锁才会阻塞,比如锁的是类实例,则同一个类的对象A,B之间不构成竞争;如果锁的是类(MyClass.class),则和类实例之间不构成竞争。
Java提供了synchronized关键字来支持内在锁。Synchronized可以放在对象,方法,类的前面。
注:这个关键字在方法前面不能被继承,不是方法签名的一部分。

1) 放在普通方法(非static)前面,锁住this对象都是锁的类实例,效果是一样的。
Public synchronized void ff() {}

Public void ff()
{
    Synchronized(this)
    {
       ….
    }
}

2) 放在普通(非static)成员变量前面(只能在同步块中锁成员变量,不能在声明变量时用synchronized,static变量也是一样)则锁住的只是该成员变量。注意锁住成员变量后不要对该变量进行重新赋值,赋值后这个方法获得的锁就不能跟其他线程再次访问这个方法构成锁竞争了,新的请求会获得新的对象锁。

如同步块:
synchronized(o)
{
    // 这里就是严重错误了。
    O = new Object();
}

同样,下面的写法毫无意义,jvm通常也会优化掉这种锁同步。
synchronized(new Object())
{
    ...
}

3) Synchronized放在static变量(只能在同步块中锁static变量,不能在声明变量时用synchronized)、static方法前面或类前面或用同步块同步Class.forName(“MyClass”)都是获取的类锁,跟类实例对象锁不一样。放在类前面则该类所有的方法都是同步的。

4) Synchronized块锁住myClass.getClass()跟上面的类锁不一样。

类锁和实例锁可以同时获得,并且互不干扰:
public class Something(){ 
        public synchronized void isSyncA(){} 
        public synchronized void isSyncB(){} 
        public static synchronized void cSyncA(){} 
        public static synchronized void cSyncB(){} 
    } 
那么,对于Something类的两个实例a与b,那么下列组方法何以被1个以上线程同时访问呢
1. x.isSyncA()与x.isSyncB()
2. x.isSyncA()与y.isSyncA()
3. x.cSyncA()与y.cSyncB()
4. x.isSyncA()与Something.cSyncA() 
答案:
1. 都是对同一个实例的synchronized域访问,因此不能被同时访问
2. 是针对不同实例的,因此可以同时被访问
3. 因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与   Something.isSyncB()了,因此不能被同时访问。
4. 能够同时访问,因为一个是实例锁,一个是类锁。

5)使用锁同步时,我们要尽量减少锁的竞争
通常可以采取:
  (1)减小锁的范围(快进快出)
  (2)减小锁的粒度,比如ConcurrentHashMap里采取的分段锁。当采取一个锁来保护多个相互独立的状态时,可以将锁分解成多个锁。
  (3)一些替代锁的方法,比如使用并发容器或原子变量。
还可以参考:
http://yoush25-163-com.iteye.com/blog/999157
http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html

可重入(reentrant)同步:线程可以重新获得他已经拥有的锁。

2.4 同步与volatile
线程读取的所有变量的值都是由内存模型来决定的,因为内存模型定义了变量被读取时允许返回的值集合。从程序员的角度看每个值几何应该只包含一个确定的值,即由某个线程最近写入的值,然而在缺乏同步时,实际获得的值集合可能包含许多不同的值。
先了解下内存模型吧。
Java内存模型 ( java memory model )
根据Java Language Specification中的说明, jvm系统中存在一个主内存(Main Memory或Java Heap Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。
每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。



其中, 工作内存里的变量, 在多核处理器下, 将大部分储存于处理器高速缓存中, 高速缓存在不经过内存时, 也是不可见的.

内存模型的特征:
a, Visibility 可视性 (多核,多线程间数据的共享)
b, Ordering 有序性 (对内存进行的操作应该是有序的)


jvm怎么体现可视性(Visibility) ?
在jvm中, 通过并发线程修改变量值, 必须将线程变量同步回主存后, 其他线程才能访问到.
jvm怎么体现有序性(Ordering) ?
通过Java提供的同步机制或volatile关键字, 来保证内存的访问顺序.
详细请参考:http://developer.51cto.com/art/200906/131393.htm

Java提供了一种同步机制,它不提供对锁的独占访问,但同样可以确保对变量的每一个读取操作都返回最近写入的值,这种机制就是用volatile变量。与synchronized相比,volatile变量所需的编码较少,并且运行时开销也少,但它的功能只是synchronized的一部分,只具备可见性不具备原子性,很容易被误用。
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
• 对变量的写操作不依赖于当前值。
• 该变量没有包含在具有其他变量的不变式中。


volatile可以解决可见性问题,但不能解决原子性问题,比如i++的操作,即使是volatile类型的也不能保证并发安全。

详细可以参考:
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

2.5 活性
并发应用程序按照及时方式执行的能力成为活性,一般包括三种类型的问题,死锁,饿死和活锁。

1) 死锁
互相等待资源而都不能运行,比如经典的哲学家用餐问题。

2) 饿死
一个线程永远无法获得共享资源的使用。

3) 活锁



2.6 threadLocal变量
ThreadLocal并不是一个线程,而是线程的局部变量,也许叫做ThreadLocalVariable更容易让人理解。ThreadLocal变量为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本而不影响其他线程。
Jdk2.0开始就提供了ThreadLocal,后来在速度上改进了,Jdk5.0提供了泛型支持,ThreadLoacal也被定义为支持泛型:
Public class ThreadLocal<T> extends Object
该类定义了4个方法:
1) protected T initialValue():返回此线程局部变量的当前线程的“初始值”。该方法定义为protected的就是为了重写的。线程第一次使用get()方法访问变量时调用此方法,但如果线程线程之前调用了set(T)方法则不会对该线程再调用此方法。通常此方法对每个线程最多调用一次,但如果在调用get()后又调用了remove()则可能再次调用此方法。通常使用匿名内部类重写此方法。
2) public T get():返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值则先将其初始化为调用initialValue方法返回的值。
3) public void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,他们只依靠initialValue方法来设置线程局部变量的值。
4) public void remove():移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程读取,且这期间当前线程没有设置其值,则将调用其initialValue方法重新初始化。
注意在线程池中尽量不要使用ThreadLocal,或要谨慎使用,使用完一定要remove,因为线程池中的线程是重复使用的,ThreadLocal被用过之后后面的线程再获得,里面的值已经不是初始化时的值,是之前被处理过的值。

还可以参考:http://www.iteye.com/topic/103804
http://lavasoft.blog.51cto.com/62575/51926/

2.7 高级并发对象
上面的重点是讲述低级别的API,都是Java平台最基本的组成部分,这些足以胜任基本的任务,但是更加高级的任务需要更高级别的API,对应充分利用现代多处理器和多核心系统的大规模并发应用程序来说尤其重要。
Jdk5.0之后引入了高级并发特性,并且不断完善。大多数特性在java.util.concurrent包中实现,java集合框架中也有新的并发数据结构补充进来。

主要新增的高级并发对象有:Lock对象,执行器,并发集合、原子变量和同步器。
1.)Lock对象
对应前面synchronized里的内部锁,lock对象是显式的,支持更加复杂的锁定语法。也支持wait和notify.
2.)执行器
增加了对线程池的支持,Executors,ExecutorService等。
3.)并发集合
主要有BlockingQueue, ConcurrentMap等。
4.)原子变量
对应在java.util.concurrent.atomic包中。
5.)同步器
提供了一些帮助在线程间协调的类,包括semaphores,mutexes,latches等。

上面讲的这些高级对象在后面会介绍,这里只是引入一下。
  • 大小: 81.1 KB
  • 大小: 7.9 KB
0
1
分享到:
评论

相关推荐

    Eclipse开发部署在Websphere的Socket服务器端应用

    这一步是构建任何Web应用程序的基础。通过这个项目,我们可以组织所有相关的源代码、配置文件和其他资源。 ### 2. 配置`web.xml` 接下来,我们需要编辑项目的`WEB-INF/web.xml`文件,这是部署描述符文件,用于配置...

    ikm_java_8.pdf

    private static final ThreadLocal&lt;Integer&gt; threadLocal = new ThreadLocal&lt;&gt;(); public static void main(String[] args) { threadLocal.set(10); // 在当前线程设置值 // 创建新线程并设置不同的值 ...

    javaWeb学习笔记基础知识

    5. **Context**:代表一个具体的Web应用程序上下文环境。 每个 **Context** 容器负责管理一个Web工程中的所有Servlet实例。 #### 三、实现Servlet的方法 实现Servlet的主要方式有三种: 1. **实现Servlet接口**...

    <<windows 程序设计>>配套代码

    配套代码中会涉及到大量的API调用,如CreateWindow、SendMessage、GetProcAddress等,这些都是构建Windows应用程序的基础。 2. **事件驱动编程**:Windows程序设计的核心是事件驱动模型,程序通过监听并响应用户的...

    weblogic 连接数为5的限制破解角决方法

    WebLogic Server是一款由Oracle公司提供的企业级Java应用服务器,它为构建、部署和管理企业级Java应用程序提供了强大的支持。在默认情况下,WebLogic Server对并发连接数有一定的限制,以保护服务器资源不受过度消耗...

    JavaServlet程序设计初步

    JavaServlet程序设计初步是IT领域中关于Web开发的一项基础技术,它是Java EE(企业版)平台的一部分,用于构建动态Web应用程序。本节将深入探讨JavaServlet的核心概念、工作原理以及如何进行基本的程序设计。 一、...

    构建线程安全应用程序

    构建线程安全的应用程序是并发编程中的一个重要议题,涉及到多个线程共享同一资源时如何保持数据的一致性与完整性。 首先,线程安全性关注的是对象在操作过程中的状态完整性。在单线程环境下,对象的操作不会被打断...

    Struts快速学习指南

    通过以上知识点的梳理,我们可以看出Struts框架不仅为Java Web开发提供了一个强大的基础框架,而且还为开发者提供了一整套完善的工具和资源,使得开发人员能够更加高效地构建高质量的Web应用程序。

    servlet基础笔记

    总结,Servlet是Java Web开发中的关键组件,理解其基础和工作原理对于构建高效、可扩展的Web应用程序至关重要。通过学习和实践,开发者可以熟练地利用Servlet来处理各种Web请求,实现复杂的业务逻辑。

    传智播客_张孝祥_java.web_Servlet开发入门第8讲

    在本课程"传智播客_张孝祥_java.web_Servlet开发入门第8讲"中,我们将深入探讨Java Servlet技术,这是Java Web开发的核心部分。...在实际项目开发中,这些技能将帮助你构建高效、可扩展的Web应用程序。

    servlets-examples.rar_servlets-examples

    Servlets是Java编程语言中用于扩展服务器功能的接口,它由Java EE(Enterprise Edition)提供,主要用于构建动态web应用程序。"servlets-examples.rar_servlets-examples"压缩包包含了一系列Servlet的示例代码,这些...

    Spring security

    **Spring Security 深度解析** Spring Security 是一个强大的安全框架,用于保护基于 Java 的 Web 应用程序。它提供了一套完整的访问控制和身份...通过不断的实践和学习,开发者可以构建出更安全、更健壮的应用程序。

    strucs1.1简介

    2. 创建一个标准的Web应用程序,确保有WEB-INF目录,其中包含classes和lib子目录以及web.xml文件。 3. 将Struts的库文件复制到你的Web应用的WEB-INF/lib目录下。 4. 修改WEB-INF/classes/web.xml文件,配置Action...

    shiro基础权限管理Demo

    Realms则连接了Shiro与应用程序的数据源,处理认证和授权信息。Cryptography部分则涉及数据的安全存储和传输。 在这个Demo中,我们可能会看到一个自定义的Realm实现,这个自定义 Realm 将连接到数据库中的用户、...

    servlet和struts2笔记

    ### servlet和struts2笔记 #### 一、Servlet 基础 ...Servlet 可以看作是服务器端的小程序,它们能够动态地生成内容。...此外,掌握 Servlet 的路径配置和线程安全问题是确保应用程序稳定性和性能的关键。

    培训资料_servlet

    2. **线程安全**:Servlet默认以多线程模式运行,因此需要特别注意线程安全问题。开发者应避免在Servlet中使用全局变量,避免并发访问时的数据冲突。 3. **Model2与MVC设计模式**:Model2是MVC设计模式在Web开发中...

    使用javaweb写Servlet

    Servlet还可以与其他Java Web组件如JSP(JavaServer Pages)、Filter和Listener协同工作,构建更强大的应用程序。 在处理请求时,Servlet可以从HttpServletRequest对象中获取请求参数,例如表单数据。同时,...

    韩顺平Servlet学习源代码

    Servlet是Java Web开发中的核心组件,它是一种服务器端的Java应用程序,主要用于处理和响应客户端(如Web浏览器)的请求。韩顺平老师的Servlet学习源代码是针对初学者的一个优秀资源,旨在帮助开发者深入理解Servlet...

    Servlet帮助文档

    Servlet帮助文档是开发人员在构建基于Servlet的应用程序时的重要参考资料。 一、Servlet基本概念 Servlet是一个Java类,它扩展了Web服务器的能力,可以接收并响应来自客户端(通常是Web浏览器)的请求。Servlet生命...

    servlet相关资料介绍.zip

    Servlet是Java Web开发中的核心组件,它是一种服务器端的Java应用程序,主要用于扩展Web服务器的功能,处理来自客户端(通常是Web浏览器)的请求并返回响应。在本文中,我们将深入探讨Servlet的相关知识点,包括其...

Global site tag (gtag.js) - Google Analytics