有关于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 的文章,有些想法,以后再写上来吧。
分享到:
相关推荐
Servlet 线程安全问题 Servlet 线程安全问题是指在使用 Servlet 编程时,如果不注意多线程安全性问题,可能会导致难以发现的错误。Servlet/JSP 技术由于其多线程运行而具有很高的执行效率,但这也意味着需要非常...
在C#编程中,多线程环境下的数据操作是一个复杂且需要谨慎处理的领域。...在使用List或其他非线程安全的数据结构时,要时刻警惕潜在的并发问题,并采取适当的同步措施,确保数据的一致性和完整性。
在计算机编程领域,尤其是涉及到实时系统和并发编程时,线程锁和线程安全变量是至关重要的概念。LabWindows/CVI是一种流行的交互式C开发环境,特别适合于开发科学和工程应用。本实例将深入探讨如何在LabWindows/CVI...
浅谈C#跨线程调用窗体控件引发的线程安全问题 C#跨线程调用窗体控件时可能会引发线程安全问题,例如当...在C#中跨线程调用窗体控件时,需要确保以线程安全方式访问控件,以避免出现不一致的状态和其他线程相关的问题。
在Java中,局部变量是线程安全的,因为每个线程都有自己的局部变量副本,它们不会被其他线程修改。然而,实例变量是共享的,因此在多线程环境下需要特别注意。 以下是一些常见的线程安全问题和解决策略: 1. 数据...
线程安全的日志库在多线程环境下尤为重要,因为不正确的日志操作可能会导致数据竞争和同步问题。本文将详细讨论如何在C++中实现一个基于Win32接口的线程安全日志库,并关注其核心概念和技术。 首先,我们需要理解...
线程安全是指在多线程环境中,一个类或函数能够按照其规格说明正确执行,即使在各种线程调度或交错执行的情况下,也不会出现竞态条件、数据竞争、死锁或其他不一致的情况。构建线程安全的应用程序是并发编程中的一个...
Servlet和Struts Action是两种常见的Java Web开发组件,它们在多线程环境下运行时可能存在线程安全问题。线程安全是指在多线程环境中,一个类或者方法能够正确处理多个线程的并发访问,保证数据的一致性和完整性。 ...
通过编写和执行这样的测试,开发者可以确保他们的代码在并发环境下能够正确地工作,避免潜在的数据不一致性和其他线程安全问题。同时,这也提醒我们在编程时要时刻注意变量的作用域和生命周期,以及它们与多线程环境...
在Java编程中,多线程安全集合是程序员在并发环境下处理数据共享时必须考虑的关键概念。这些集合确保了在多个线程访问时的数据一致性、完整性和安全性,避免了竞态条件、死锁和其他并发问题。Java提供了一系列的线程...
在IT行业中,尤其是在开发高并发应用时,线程安全是一个至关重要的问题。"C# 高效线程安全,解决多线程写txt日志类.zip" 提供了一个专门用于多线程环境下写入txt日志文件的解决方案,确保了在并发写入时的数据一致性...
Java线程安全是多线程编程中的一个关键概念,它涉及到在并发环境下如何正确地管理共享资源,确保程序的正确性和一致性。以下是对Java线程安全的深入总结: ### 一、线程安全的定义 线程安全是指当多个线程访问同一...
在Java编程语言中,线程安全是多线程环境下程序正确性和稳定性的重要概念。线程安全测试类的设计是为了确保在并发环境中,多个线程访问共享资源时不会导致数据的不一致或异常行为。本测试主要关注`synchronized`...
在IT行业中,线程安全是多线程编程中的一个重要概念,确保多个线程并发执行时,数据的正确性和完整性不会受到影响。线程安全通常通过同步机制来实现,其中包括原子操作和锁机制。本文将深入探讨易语言中的原子锁与...
操作系统课程设计中实现线程安全的双向链表是一项重要的实践任务,这涉及到多线程编程、数据结构以及并发控制等核心知识点。在这个项目中,我们主要关注如何在多线程环境下构建一个能够正确操作(如插入、删除)而不...
然而,ArrayList本身并不是线程安全的,这意味着在多线程环境中,多个线程同时访问和修改ArrayList时,可能会出现数据不一致或者竞态条件等问题。本测试着重于分析ArrayList在并发环境下的行为,并探讨如何确保其...
在C#编程中,线程安全是多线程应用程序中至关重要的一个方面,尤其是在处理共享资源如文本日志文件时。本主题将深入探讨如何在C#中创建一个高效的线程安全日志类,用于在多线程环境中安全地写入txt日志。 首先,...
同时,线程安全函数所调用的其他函数也必须是线程安全的,否则同样需要互斥锁保护。 文中提到的POSIX标准函数basename()就是一个例子。在Linux/UNIX平台上,POSIX标准函数中并没有所有的函数都具备线程安全版本。...
然而,当涉及到多线程环境时,STL并非完全线程安全的。这意味着在多个线程同时访问和修改STL容器时,如果不采取适当的同步措施,可能会出现数据竞争、异常或不一致的结果。 首先,让我们看看标题和描述中提到的...
本文将深入探讨如何使用C++进行hiredis的封装,以实现线程安全的Redis客户端操作。 首先,hiredis是Redis官方提供的一个纯C语言的简单协议解析库,它专注于处理Redis命令和响应,而忽略了更高级别的抽象。为了在C++...