您好,欢迎来到汇智旅游网。
搜索
您的当前位置:首页线程安全及其他

线程安全及其他

来源:汇智旅游网

有关于Servlet 的线程安全,首先需要知道的是,在一般情况下,每个Servlet 在容器里都只有一个实例(instance), 而每当有用户访问该Servlet 时,容器都会产生一个线程。

这是比较基本的概念了。一般我们还知道,Java 有一个Marker Interface 叫做SingleThreadModel, 这个接口一旦被继承,就意味着容器一般对一个instance 只维护一个线程。当时设计这个接口的用意自然是为了线程安全的问题。而现在,这个接口已经被废弃了。(然而,偶尔有些用老版本的系统会涉及到)。看一下API 文档中对此的说明:

Ensures that servlets handle only one request at a time.  ... If a servlet implements this interface, you are guaranteed that no two threads will execute concurrently in the servlet's service method.

然而很显然,这样做必然会极大损失效率。所以,很多容器会 maintaining a pool of servlet instances 去挽回一些损失。

本来行文到此应该结束了。因为笔者正好想到有关JVM结构的一篇文章,所以有了以下的思考

对于实例,线程,说得最多的必然是线程安全的话题。线程安全本文从三个方面来讲:

1、 虚拟机。虚拟机的一个特点就是它真实地模拟了计算机硬件对于程序的处理。虚拟机有多个重要部件,我们这里牵涉到的主要是方法域,堆,程序计数器以及Java 栈。

对于实例来讲,其与方法域和堆是一一对应的。也就是说,每个实例都有其唯一的方法域和堆。而对于程序计数器以及Java 栈,他们对应的则是线程。(即每个实例的线程都有相应的程序计数器以及Java 栈)。并且,对于实例,是可以有多个线程共享的,也就是说,实例的方法域和堆是可以由该实例的多个线程共享的。

堆的共享,一方面是Java中线程通讯的基础,一方面也是产生线程问题的一个基本原因。

2。Java语言的内存模型。在Java 中,有很多种变量。从类型上讲,基本类型的(primitive),或者对象类型的。从范围上讲,实例变量,类变量,函数内部声明的变量;从修饰符讲,有 final, volatile 等。

而volatile 和final 变量。volotile 修饰符的意义一般被解释是为了告知编译器此变量将经常变化,不需要编译器对其优化使用寄存器。而很少有说到的是,这个“经常变化”的变量,往往是线程共享的一些变量。在一份文档里有这样一小段程序:如果在多个线程中运行上面的函数foo和bar,这上面的变量stop 和num 就是刚才所说的“经常变化”的变量。而volatile 还有另外一个作用。

class  Test{ 
 
private   boolean stop  =   false ;  
private   int  num  =   0 ;  

public   void  foo()  {    
num  =   100 ;  
stop  =   true ;  
// ...  


 
public   void  bar()  {   
 
if  (stop)     
 num 
+=  num;   // num can be  0

 
// ...}

 

在JSR133中说到,一般jvm 会对其认为不会影响上下文的程序做适当优化。这其中的一个优化是:re-order. 也就是说,在上面的程序foo 函数中,两个语句的执行顺序可能被交换。这就是上面注释中num can be 0 的由来。这与volatile 有什么关系呢?事实是,如果对上面的两个变量加上volatile修饰符,上述的re-order就不会发生。

According to the JLS, because stop and num are declared volatile, they should be sequentially consistent. This means that if stop is ever true, num must have been set to 100. However, because many JVMs do not implement the sequential consistency feature of volatile, you cannot count on this behavior.

再说一个final.  final 和线程安全产生关系的理由是,它可以较好的预防此类问题。一般来说,final 修饰的实例变量或者类变量,它的值在所在类的构造函数完成之后就固定了。“所在类的构造函数完成”有两种写法:一种是在声明field时就赋值,一种是在构造函数中赋值。使用final之后,只要能够保证在构造函数的时候处理好线程问题就可以了。当然,final 之后不能被修改,注定它只适用一小部分情况。

3、线程问题的解决。一般说到线程安全,大家都会想到synchronized. synchronized 的使用分很多种,包括直接在方法签名中声明,使用synchronized 块等。另外,需要说明的还有两点:

首先是关于synchronized 的原理。在JSR133 中说到,synchronized 的函数表示其获取了某个monitor,这个monitor 通常是某个对象的引用。而后synchronized 对monitor 加锁,写成程序像:

synchronized (monitor){ ...//...}

要访问同一对象的synchronized 方法,必须要获得该monitor 并加锁才可以。我们以前说,锁的target 是对象本身。其实是因为,这个monitor 往往是这个对象的某个实例变量或者干脆就是this. 另外,很多人在程序里写synchronized(lock), 这类命名,其实是不太合适的。

第二,对于static 的方法中使用synchronized 块时,选择的被锁定对象是Object.class. 比如对于上面的例子,就是Test.class。(不是Test.getClass).

关于线程安全先写到这里,jsr133还没看完,今天看到的一篇关于singleton 和double check 的文章,有些想法,以后再写上来吧。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- hzar.cn 版权所有 赣ICP备2024042791号-5

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务