`
m635674608
  • 浏览: 4932639 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

有状态as无状态之线程问题

    博客分类:
  • java
 
阅读更多
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。

线程安全性:
一个类是线程安全的是指在被多个线程访问时,类可以持续进行正确的行为.不用考虑这些线程运行时环境下的调度和交替.

编写正确的并发程序的关键在于对共享的,可变的状态进行访问管理.
解决方案有两种:
1. 控制资源访问.通过锁机制来对资源的访问进行排队.这样来避免一个线程修改其他线程正在使用的对象
2. 要确保当一个线程修改了对象的状态后,其他的线程能够真正知道这种变化.

资源访问控制
-------------------------------
1.无状态 的类肯定是线程安全的,因为它不会存在交替的情况.因为所有要用到的资源都是通过参数传进去的.这样就不会存在多个线程共享资源的问题.

2.如果是有状态 的类,比如它有个属性是long count;它有个方法,是让它自增:count++;http://blog.sina.com.cn/s/blog_5f54f0be0100vwh8.html 此文中已经介绍了该操作并发的风险.在代码中该操作看起来是一个单独的操作,但它实际上是由三个操作组成的.所以它不是单独的,不可分割 的.即:"原子性 ".原子性不能指程序上的最基本的数字逻辑操作,而是逻辑上的不可分割的操作.

JAVA提供了一些线程安全的类,也就是实现了原子性的类.对这些类的操作是原子性的.它们是 在:java.util.concurrent.atomic包中.比如有类:AtomicLong,它是Long的原子化类.我们对long类型的 count进行自增操作时,不是原子性的,但对AtomicLong调用:incrementAndGet()即是原子操作的,JAVA为我们解决了这些 问题.

同时,JAVA提供了我们自己可控制的原子机制-- .
JAVA提供了强制原子性的内置锁机制:synchronized .
我们通过 synchronized 给一个类,或一个方法或一个属性或一串操作进行锁标识.线程进入synchronized 之前会自动获得锁;在正常退出,或出现异常时,线程都会释放锁.被锁上后,其它线程只有等到锁被释放才能进入.否则只有一直等下去.所以这种做法在有些时候会极端影响效率.(静态属性或方法的锁是从Class对象上获取的)

当一个线程请求其他线程已经占有的锁时,请求被阻塞.但占有锁的那个线程是可以再次请求的.这就意味着:锁的基于线程的而不是基于请求.实现这种机制是为 每个锁关联一个请求计数和一个占有它的线程.当计数为0时,表示该锁未被占有.此时线程请求时,JVM将记录锁的占有线程,并将请求计数加1.如果同一线 程再次请求这个锁,计数再加1.每次退出synchronized 标识的块时计数会减1.当计数为0时,锁被释放.

并不是所有的数据都需要锁保护--只有那些被多个线程访问的可变数据才需要.过多的synchronized 会影响性能.所以我们最好是将一些需要同步的原子操作放在同步块中.如下面这种做法:

synchronized ( this ) {

            ++ hits ;

            if (i.equals( lastNumber )) {

                ++ cacheHits ;

                factors = lastFactors .clone();

            }

        }

        if (factors == null ) {

            factors = factor(i);

            synchronized ( this ) {

                lastNumber = i;

                lastFactors = factors.clone();

            }

        }

如上所示.两个分离的synchronized 块中都只有很简短的代码.第一个块保护着检查再运行的操作以检查对我们很重要的状态码,另一个进行数据的更新.

共享对象
-------------------------------
同步的可见性:
使用了synchronized进行加锁后,一个线程在该同步块内做的操作对接下来的线程是可见的.这就是"同步"的含义.
1. 当一个读线程和一个写线程同时进行时,我们不能保证读线程能及时地读取写线程写入的值.除非使用synchronized进行同步.例如下面代码所示:

private static boolean ready ;

    private static int number ;

    private static class ReaderThread extends Thread {

        public void run() {

            while (! ready )

                Thread.yield ();

            System. out .println( number );

        }

    }

    public static void main(String[] args) {

        new ReaderThread().start();

        number = 42;

        ready = true ;

    }

上面的mian主线程运行时还充当了"写线程",并且新建"读线程"并让它运行.读线程会不断的循环直到ready的值为true.但在有些情况下,上面的程序会和我们想象的输入42相异:

由于JAVA的"重排序 "机制(JVM:只要代码顺序改变对结果不产生影响,那么就不能保证代码执行的顺序是书写的顺序)可能在对number设置值前ready的值就已经是true了.那么输入的结果会是0.

2. 在没有同步时,我们可能就象上面一样,获得到的数据不是最新设置进去的.如:一个类有一属性,并且有它的getter,setter方法,当两个线程一个执行getter一个执行setter时,就容易出现获得到"过期数据 ".但给getter,setter方法加上synchronized 后可以解决这一问题.

除了过期数据,还可能出现错数据,这种问题只是存在于64位的数据.由于JVM的运算是基于32位的.即:不管是布尔值(1位),short(16位),运算时,都通过左侧补零将它扩展成32位,然后进行运算.而float,double,long 等64位的数据则被做为两个32位数进行运算.
所以,在多线程未同步时,64位数据的读取可能会返回一个值的前32位,及另一个
值的后32位.通过给值加上volatile 标记可以让JVM避免这种问题.如:volatile float test;

当一个域声明为volatile 类型后,编译器与运行时会监视这个变量,而且对它的操作不会与其他的内在操作重排序.它不会缓存在寄存器或者缓存在其它地方,所以读一个volatile 类型的变量时,它总是返回由某一线程所写入的最新值.我们可以将它看做轻量级的同步机制.

  private int value ;

       public synchronized int get() {

        return value ;

    }

       public synchronized void set( int value) {

        this . value = value;

    }

如上代码可以被:volatile private int value;以及不加同步声明的getter,setter方法所代替.但当然会牺少许功能:加锁可以保证可见性和原子性,但volatile变量只能保证可见性 .所以,在不需要原子性的时候,可以用它.

EJB中的有状态与无状态:

1.Stateful session bean的每个用户都有自己的一个实例,所以两者对stateful session bean的操作不会影响对方。另外注意:如果后面需要操作某个用户的实例,你必须在客户端缓存Bean的Stub对象(JSP通常的做法是用 Session缓存),这样在后面每次调用中,容器才知道要提供相同的bean实例。

2.Stateless Session Bean不负责记录使用者状态,Stateless Session Bean一旦实例化就被加进会话池中,各个用户都可以共用。如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响。

3.从内存方面来看,Stateful Session Bean与Stateless Session Bean比较,Stateful Session Bean会消耗J2EE Server 较多的内存,然而Stateful Session Bean的优势却在于他可以维持使用者的状态。

Spring中的有状态(Stateful)和无状态(Stateless)

1.通过上面的分析,相信大家已经对有状态和无状态有了一定的理解。无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式 。Prototype: 每次对bean的请求都会创建一个新的bean实例。

2.默认情况下,从Spring bean工厂所取得的实例为singleton(scope属性为singleton),容器只存在一个共享的bean实例。

3.理解了两者的关系,那么scope选择的原则就很容易了:有状态的bean都使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

4.如Service层、Dao层用默认singleton就行,虽然Service类也有dao这样的属性,但dao这些类都是没有状态信息 的,也就是相当于不变(immutable)类,所以不影响。Struts2中的Action因为会有User、BizEntity这样的实例对象,是有 状态信息的,在多线程环境下是不安全的,所以Struts2默认的实现是Prototype模式。在Spring中,Struts2的Action 中,scope要配成prototype作用域。

Servlet、Struts中的有状态和无状态:

1.Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web 容器负责的。一个Servlet类在Application中只有一个实例存在,也就是有多个线程在使用这个实例。这是单例模式的应用。无状态的单例是线 程安全的,但我们如果在Servlet里用了实例变量,那么就变成有状态了,是非线程安全的。如下面的用法就是不安全的,因为user,out都是有状态 信息的。

/**
 * 非线程安全的Servlet。
 * @author Peter Wei
 *
 */
public class UnSafeServlet HttpServlet{
   
    User user;
    PrintWriter out;
   
    public void doGet (HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
        //do something...
    }
}

Out,Request,Response,Session,Config,Page,PageContext是线程安全的,Application在整个系统内被使用,所以不是线程安全的.

2.Struts1也是基于单例模式实现,也就是只有一个Action实例供多线程使用。默认的模式是前台页面数据通过actionForm传 入,在action中的excute方法接收,这样action是无状态的,所以一般情况下Strunts1是线程安全的。如果Action中用了实例变 量,那么就变成有状态了,同样是非线程安全的。像下面这样就是线程不安全的。

/**
 * 非线程安全的Struts1示例
 *
 * @author Peter Wei
 *
 */
public class UnSafeAction1 extends Action {

    // 因为Struts1是单例实现,有状态情况下,对象引用是非线程安全的
    User user;

    public void execute() {
        // do something...
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

 

3.Struts2默认的实现是Prototype模式。也就是每个请求都新生成一个Action实例,所以不存在线程安全问题。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。

4.如何解决Servlet和Struts1的线程安全问题,当我们能比较好的理解有状态和无状态的原理,自然很容易得出结论:不要使用有状态的bean,也就是不要用实例变量 。 如果用,就要用prototype模式。Struts1 user guide里有: Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class.

总结:
Stateless无状态用单例Singleton模式,Stateful有状态就用原型Prototype模式。
Stateful 有状态是多线程编码的天敌,所以在开发中尽量用Stateless无状态,无状态是不变(immutable)模式的应用,有很多优点:不用管线程和同步的问题 ,如果值是不可变的,程序不用担心多个线程改变共享状态,所以可以避免线程竞争的bugs. 因为没有竞争,就不用用locks等机制,所以无状态的不变机制,也可以避免产生死锁现象。

分享到:
评论

相关推荐

    asp.net线程批量导入数据时通过ajax获取执行状态

    通过线程执行导入,并把正在执行的状态存入session,既共享执行状态,通过ajax调用session里的执行状态,从而实现反馈导入状态的功能! 上代码: 前端页面 <!DOCTYPE html> <html lang=en> <head> ...

    Python 多线程其他属性以及继承Thread类详解

    2.threading.enumerate:返回一个包含正在运行的线程的list,正在运行的线程指的是线程启动后,结束前的状态 3.threading.activeCount:返回正在运行的线程数量,效果跟len(threading.enumer)一样 4.thr.setName:给...

    使用ASP.NET创建线程实例教程

    使用 ASP.NET 创建一个线程的实现方法其实非常简单,只需将其声明并为其提供线程起始点处的方法...ASP.NET的Start方法用来使线程被安排进行执行,它有两种重载形式,下面分别介绍。 (1)导致操作系统将当前实例的状态

    Android-MultiDownloader_as:Android框架来处理多个线程的并发

    观察者模式当您有一些对象必须将一个对象的状态更改通知所有感兴趣的对象时,将使用观察者模式。 在图形上,这是这样工作的: 下图是它的uml表示:让我们建立模型观察者接口可以使用以下结构构建: public interface...

    Tensorflow 多线程与多进程数据加载实例

    在项目中遇到需要处理超级大量的数据集,无法载入内存的问题就不用说了,单线程分批读取和处理(虽然这个处理也只是特别简单的首尾相连的操作)也会使瓶颈出现在CPU性能上,所以研究了一下多线程和多进程的数据读取和...

    as.net本质论

    第5~8章是经典的Web Form部分,重点讲解了控件的原理与页面的生成机制,包括流与控件的关系、控件与页面的关系、数据绑定控件与模板的关系,以及ASENET中的各种状态管理技术。第9章分析了ASENET。MVC的处理过程,...

    CLR via C# 3rd Edition

    全新的一章,介绍了Windows支持线程的原因、线程开销、CPU动态、NUMA架构、CLR线程和Windows线程的关系、Thread类、使用线程的理由、线程调度和优先级、前台线程和后台线程。 Chapter 26-Performing Compute-Bound...

    asf.zip_java jmx jconsole_remote

    JDK1.5提供JMX remote的管理工具Jconsole,可以监控Java运行程序的内存使用情况、活动线程数量、类装载的数量、MBeans的状态、虚拟机的各种信息等,还可以执行MBean公开的方法或强制进行垃圾回收。只要应用服务器...

    asyncio-executor:协程执行器,起一个额外的线程执行事件循环,主线程则管理这个事件循环线程, 这个执行器不要用在协程中

    异步执行器版本:0.0.4 状态:开发人员作者:hsz 电子邮件: 描述用于运行协程的Asyncio执行程序。 这段代码来自< > 关键字:asyncio,执行器特征异步运行协程例子通过使用提交运行协程from concurrent . futures...

    颜色分类leetcode-tf-matplotlib:将matplotlib图形无缝集成为tensorflow摘要

    线程问题, 支持多个数字和, 为运行时关键绘图提供 blitting。 以下 TensorFlow 摘要由 . 它在测试表面上绘制了梯度下降优化器的进度。 为了避免重绘测试表面,它使用了位块传输。 有关更多介绍性示例,请参见下文...

    net学习笔记及其他代码应用

    答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。 40.接口是否可...

    千方百计笔试题大全

    73、线程的基本概念、线程的基本状态以及状态之间的关系 18 74、sleep() 和 wait() 有什么区别? 18 75、socket通信(tcp/udp区别及JAVA的实现方式) 18 76、什么是java序列化,如何实现java序列化? 18 77、简述...

    java面试宝典

    73、线程的基本概念、线程的基本状态以及状态之间的关系 18 74、sleep() 和 wait() 有什么区别? 18 75、socket通信(tcp/udp区别及JAVA的实现方式) 18 76、什么是java序列化,如何实现java序列化? 18 77、简述...

    Java应用日志框架TNT4J.zip

    这个API是专门用以解决分布式,并发,多线程,多用户应用,包括活动的相关性,应用程序的状态转储,性能和用户定义的量度。 以下是它的特性: Simple programming model to facilitate fast root-cause, log ...

    AsSQL:一个旨在使 PHP 中的异步 MySQL 查询更容易的库

    理想情况下,我们有第二个线程等待所有连接上的活动。 不幸的是,我们在 PHP 中没有用户态线程的奢侈(除非您想尝试 pthreads 并使这个线程安全 - 继续)。 因此,您需要在代码中添加一些内容来定期检查查询的状态...

    aiofiles:对asyncio的文件支持

    普通的本地文件IO处于阻塞状态,无法轻松,便携地实现异步。 这意味着执行文件IO可能会干扰asyncio应用程序,该应用程序不应阻塞正在执行的线程。 aiofiles通过引入支持将操作委派给单独的线程池的异步版本的文件来...

    通用网络游戏通讯平台

    BS.Play 的核心服务器部分 Comoro ,运行于*NIX 平台上,使用 C 语言开发,采用单进程静态多线程的事件 驱动模型,可以作为普通应用程序和守护进程执行。 Comoro 可以分别部署为 Coin Server、Login Server、Base ...

    A3C-tensorflow:A3C张量流实现

    为避免多线程问题,必须对啤酒进行修改 用法 $ python main.py 有几种选项可以更改学习参数和行为。 rom:要播放的Atari rom文件。 默认为breakout.bin。 threads_num:要并行运行的学习者线程数。 默认为8。 ...

    Windows Sockets网络编程 可能是最清晰版本(Windows Sockets 2规范解释小组负责人亲自执笔。)总共4个包,part1

    5.7.1 线程化的应用程序 5.7.2 其他适用性说明 第6章 socket状态 6.1 什么是socket状态 6.1.1 数据报socket的状态 6.1.2 流socket的状态 6.2 socket状态的检测方法 6.2.1 根据函数调用的成功或失败检测 6.2.2 同步...

    Windows Sockets网络编程 总计4个包,part2

    5.7.1 线程化的应用程序 5.7.2 其他适用性说明 第6章 socket状态 6.1 什么是socket状态 6.1.1 数据报socket的状态 6.1.2 流socket的状态 6.2 socket状态的检测方法 6.2.1 根据函数调用的成功或失败检测 6.2.2 同步...

Global site tag (gtag.js) - Google Analytics