Java后端易忽略的问题

本文详细介绍了有状态Bean和无状态Bean的区别及其在多线程环境下的应用,包括如何通过ThreadLocal改造非线程安全对象以实现线程安全,并讨论了Spring框架下Bean的生命周期管理与垃圾回收机制。
摘要由CSDN通过智能技术生成

1、有状态bean和无状态bean

  有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的,在不同方法调用间不保留任何状态

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

/** 
 * 有状态bean,有state,user等属性,并且user有存偖功能,是可变的。 
 */  
public class StatefulBean {  

    public int state;  
    // 由于多线程环境下,user是引用对象,是非线程安全的  
    public User user;  

    public int getState() {  
        return state;  
    }  

    public void setState(int state) {  
        this.state = state;  
    }  

    public User getUser() {  
        return user;  
    }  

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

/** 
 * 无状态bean,不能存偖数据。因为没有任何属性,所以是不可变的。
 * 只有一系列的方法操作。 
 */  
public class StatelessBeanService {  
   //虽然有billDao属性,但billDao这个对象本身也是没有状态信息的,所以是Stateless Bean.  
    private BillDao billDao;  

    public BillDao getBillDao() {  
        return billDao;  
    }  

    public void setBillDao(BillDao billDao) {  
        this.billDao = billDao;  
    }  

    public List<User> findUser(String Id) {  
return null;  
    }  
}  

  无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。

  有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype:每次对bean的请求都会创建一个新的bean实例

  一般情况下,只有无状态bean才可以在多线程环境下共享(既然没有状态,不能保存数据,当然随便共享啦)

  在spring中,绝大部分Bean都可以声明为singleton作用域。如果在<bean>标签中指定Bean的作用范围是 scopt=”prototype”,那么系统将bean返回给调用者,spring就不管了。如果两个实例调用的话,每一次调用都要重新初始化,一个实例的修改不会影响另一个实例的值。如果指定Bean的作用范围是scope=”singleton”,则把bean放到缓冲池中,并将bean的引用返回给调用者。这个时候,如果两个实例调用的话,因为它们用的是同一个引用,任何一方的修改都会影响到另一方。

  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。一个Servlet类在Application中只有一个实例存在,也就是有多个线程都在使用这个实例,这是单例模式的应用。如Service层、Dao层用默认Singleton就行,虽然Service类也有dao这样的属性,但dao这些类都是没有状态信息的,也就是相当于不变(immutable)类,所以不影响。Struts2中的Action因为会有User、BizEntity这样的实例对象,是有状态信息的,在多线程环境下是不安全的,所以Struts2默认的实现是Prototype模式。在Struts2的Action中,scope要配成prototype作用域。

2、Spring对有状态bean的改造

  非线程安全对象:

public class TopicDao {
    //一个有状态的、非线程安全的变量
    private Connection conn;
    public void addTopic() throws SQLException {
        Statement stat = conn.createStatement();
        //...
    }
}

  由于TopicaDao的属性conn是非线程安全的成员变量,因此addTopic()方法也是非线程安全的,每次使用TopicDao时都必须新创建一个TopicDao实例(非singleton)。对非线程安全的conn属性进行如下改造:

public class TopicDao {
    private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>;
    public static Connection getConnection() {
        if(connThreadLocal.get() == null) {
            /**
            * connTheadLocal中没有本线程对应的Connection副本。
            * 则创建新Connection对象,并把它添加到ThreadLocal中
            */
            Connection conn = ConnectionManager.getConnection();
            connTheadLocal.set(conn);
            return conn;
        } else {
            //有的话直接返回本地变量
            return connTheadLocal.get();
        }
    }

    public void addTopic() throws SQLException {
        Statement stat = conn.createStatement();
        //...
    }
}

  spring对那些个有状态bean使用ThreadLocal维护变量(仅仅是变量,因为线程同步的问题就是成员变量的互斥访问出问题)时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

  正因为Spring对一些Bean(RequestContextholder、TransactionSynchronizationManager、 LocaleContextHolder等)中非线程安全的”有状态对象”采用了ThreadLocal封装,让它们成为线程安全的“有状态对象”,因此有状态的bean就能够以Singleton方式在多线程中正常工作了。

  由于Spring MVC默认是Singleton的,所以会产生一个潜在的安全隐患。根本核心是instance的变量保持状态的问题。这意味着每个request过来,系统都会用原有的instance去处理,这样带来了两个结果(单例的好处):
  一是我们不用每次创建Controller;
  二是减少了对象创建和垃圾收集的时间;

  由于只有一个Controller的instance,当多个线程同时调用它的时候,它里面的instance变量(可以理解为私有变量)就不是线程安全的了,会发生窜数据的问题。当然大多数情况下,我们根本不需要考虑线程安全的问题,比如dao、service等,除非在bean中声明了实例变量。因此,我们在使用Spring Mvc 的Contrller时,应避免在Controller中定义实例变量。 如:

public class Controller extends AbstractCommandController {
    protected Company company;
    protected ModelAndView handle(HttpServletRequest request,
        HttpServletResponse response,
        Object command,
        BindException errors) throws Exception {
        //...
    }
}

  在这个contrller里声明了一个变量company,这里就存在并发线程安全的问题。如果控制器是使用单例模式,且controller中有一个私有的变量a,多个请求到达同一个controller时,使用的a变量就是共用的,即若是某个请求修改了这个变量a,则在别的请求中能够读到这个修改的内容。

  有几种解决方法:
  1、在控制器中不使用实例变量;
  2、将控制器的作用域从单例改为原型,即在spring配置文件controller声明 scope=”prototype”,每次请求都创建新的controller;
  3、在Controller中使用ThreadLocal变量;

  这几种做法有好有坏,第一种,需要开发人员拥有较高的编程水平与思想意识,在编码过程中力求避免出现这种BUG, 而第二种则是容器自动的对每个请求产生一个实例,由JVM进行垃圾回收,因此做到了线程安全。使用第一种方式的好处是实例对象只有一个,所有的请求都调用该实例对象,速度和性能上要优于第二种,不好的地方,就是需要程序员自己去控制实例变量的状态保持问题。第二种由于每次请求都创建一个实例,所以会消耗较多的内存空间。所以在使用spring开发web时要注意,默认Controller、Dao、Service都是单例的

3、Java GC是在什么时候,对什么东西,做了什么事情

  在什么时候?
  1、新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。

   2、大对象以及长期存活的对象直接进入老年区

  3、当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要进入老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得更多老年区的空间

  对什么东西?
  从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。

  做什么?
  新生代:复制清理;
  老年代:标记-清除和标记-压缩算法;
  永久代:存放Java中的类和加载类的类加载器本身。

  GC Roots都有哪些: 1、 虚拟机栈中的引用的对象。2、 方法区中静态属性引用的对象,常量引用的对象。3、本地方法栈中JNI(即一般说的Native方法)引用的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值