有关于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 等。
对于基本类型,他们往往是放在栈中。笔者认为,其(访问)地址是在编译期间就获得的。而对象类型的稍微麻烦一点,堆栈里往往放的是对象的引用(当然,对堆栈的访问地址也是在编译期间决定),而实际的对象则在堆中。(这有些类似于汇编语言的间接访问,笔者认为这也是内存可以动态分配的根本)。
对于函数内部的普通变量,不会有线程安全的问题。为什么呢?因为这些变量的引用(或者可以解释为入口)都是保存在Java 栈里的,而Java 栈都是线程独立的。(关于这个,笔者的一个假设是,在获得引用地址之前,JVM 无法访问到对象)。对于实例变量, 可能稍微麻烦一些。因为其对应的实例是线程共享的,也就是说,实例变量也会共享。这就是线程安全问题的由来。对于类变量,其在线程安全方面的用法类似实例变量(当然,其实是有不同的)。
而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 的文章,有些想法,以后再写上来吧。
分享到:
相关推荐
下面小编就为大家分享一篇浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题,具有很好的参考价值,希望对大家有所帮助
java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...
当对一个复杂对象进行某种操作时,从操作开始到操作结束,被操作的对象...如果其他线程企图访问一个处于不可用状态的对象,该对象将不能正确响应从而产生无法预料的结果,如何避免这种情况发生是线程安全性的核心问题
同时根据部分易于产生线程安全问题的情况,针对C++编写多线程程序给出了一些实用的技术及相关代码供大家参考。这部分内容的阅读需要读者对C++有一定的了解;当然就其给出的思想也部分适用于其它面象对象的编程语言。
线程安全 保护“共享数据” 低级并发工具 原子变量 锁(内部锁和显式锁) 线程安全容器 同步容器 并发容器 阻塞队列 高级线程协作工具 信号量 闭锁 关卡 fork-join Executor部分 ...
(******************************************************************************) (* 模 块 名: HSLogger4D.Pas *) (* 别 名: 多任务线程安全日志接口-进程独立版 *) (* 作 者: Unsigned(僵哥) *) ...
Go包速度提供了一个线程安全计数器,用于在指定的时间内测量刻度。
什么是线程安全问题? 当 多个线程 同时共享同一个 全局变量 做 写 的操作时候,可能会受到其他线程的干扰,导致数据 脏读 。(数据一致性问题) 如何解决线程安全问题? 核心思想:在同一时刻,只能有...
多线程安全问题和锁 文章目录多线程安全问题和锁线程在jvm中的特点锁的出现synchronized 关键字-监视器锁monitor lock死锁的产生和避免 什么是线程安全问题? 当多个线程同时操作同一个数据是,可能会出现数据不一样...
线程安全与锁优化:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者再调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。
虽然微软提供了 SuspendThread、TerminateThread 等函数“似乎”可以完成这个功能,但如果你在代码里使用这些函数,则往往会遇到各种问题(比如SuspendThread时正好在CRT分配内存的锁中造成其他线程也跟着锁死,...
3. 线程安全:项目中使用了Java的synchronized关键字和Lock接口来确保线程安全。这样可以避免多个线程同时访问共享资源,从而避免数据不一致和其他潜在问题。 4. 基于Http协议:项目使用了Java的HttpURLConnection...
Go中的线程安全循环缓冲区(环形缓冲区),实现了io.ReaderWriter接口
我们既然要通过检查key是否存在(存在表示有线程在修改资源,资源上锁,其他线程不可同时操作,若key不存在,表示资源未被线程占用,允许线程抢占,然后将通过setnx设置vlaue,表示资源上锁,其他线程不可同时操作)...
ump 一个通用的线程安全内存池
访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问...
它展示了如何与非线程安全的类共同工作,并特别关注于Swing的线程问题。新增加的一章介绍了如何为多处理器机器编写并行代码。 简而言之,本书的新版涉及了有关线程的方方面面,从最简单的动画applet到最复杂的应用...
非线程安全的类 总结 第九章 多处理器机器上的并行化 单线程程序并行化 内层循环线程化 循环输出 多处理器扩展 总结 第十章 线程组 线程组概念 创建线程组 线程组方法 操作线程组 线程组、线程和安全 总结
另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消...