Servlet单实例以及线程安全小结

<%@ page isThreadSafe="true|false" %> 默认值为true isThreadSafe=false模式表示它是以Singleton模式运行。   该模式implements了接口SingleThreadMode,   该模式同一时刻只有一个实例,不会出现信息同步与否的概念。   若多个用户同时访问一个这种模式的页面,   那么先访问者完全执行完该页面……

isThreadSafe=false模式表示它是以Singleton模式运行。
     该模式implements了接口SingleThreadMode,
     该模式同一时刻只有一个实例,不会出现信息同步与否的概念。
     若多个用户同时访问一个这种模式的页面,
     那么先访问者完全执行完该页面后,后访问者才开始执行。

isThreadSafe=true模式表示它以多线程方式运行。
    该模式的信息同步,需访问同步方法(用synchronized标记的)来实现。
    一般格式如下:
    public synchronized void syncmethod(...){
      if(...) {
         wait();
      }
      notifyAll();
    } 


众所周知,Servlet为单实例多线程,非线程安全的。

若一个Servlet对应多个URL映射,那么将会生成一个还是多个Servlet实例呢?

最好的办法就是动手体验一下

@WebServlet({ "/demoServlet1", "/demoServlet2" })

public class DemoServlet extends HttpServlet {

private static final long serialVersionUID = 1L;



protected void doGet(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

PrintWriter out = response.getWriter();

out.println(request.getServletPath() + "[" + this + "]");

out.flush();

out.close();

}

}

输出结果:

/demoServlet1[com.learn.servlet3.thread.DemoServlet@1320a41]
/demoServlet2[com.learn.servlet3.thread.DemoServlet@9abce9]

输出结果可以看到映射/demoServlet1/demoServlet2对应Servlet实例是不同的。

结果证明:Servlet将为每一个URL映射生成一个实例;一个Servlet可能存在多个示例,但每一个实例都会对应不同的URL映射。

注:servlet本身就是单实例多线程的,可以多个请求同时访问一个servlet,如果改为单线程的话,若有多个并发请求那么速度会有多慢可想而知。

下面讨论单个Servlet、多线程情况下保证数据线程同步的几个方法。

  1. synchronized:代码块,方法
    大家都会使用的方式,不用详细介绍了。
    建议优先选择修饰方法。
  2. volatile
    轻量级的锁,可以保证多线程情况单线程读取所修饰变量时将会强制从共享内存中读取最新值,但赋值操作并非原子性。
    一个具有简单计数功能Servlet示范:
    /**
    
        * 使用Volatile作为轻量级锁作为计数器
    
        * 
    
        * @author yongboy
    
        * @date 2011-3-12
    
        * @version 1.0
    
        */
    
        @WebServlet("/volatileCountDemo")
    
        public class VolatileCountServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        
    
        private volatile int num = 0;
    
        
    
        protected void doGet(HttpServletRequest request,
    
        HttpServletResponse response) throws ServletException, IOException {
    
        addOne();
    
        response.getWriter().write("now access num : " + getNum());
    
        }
    
        
    
        /**
    
        * 读取开销低
    
        */
    
        private int getNum() {
    
        return num;
    
        }
    
        
    
        /**
    
        * 其写入为非线程安全的,赋值操作开销高
    
        */
    
        private synchronized void addOne() {
    
        num ++;
    
        }
    
        }
    
        
    我们在为volatile修饰属性赋值时,还是加把锁的。
3,   ThreadLocal(线程安全
可以保证每一个线程都可以独享一份变量副本(相当于把原来的servlet复制了多份并不影响原来的),每个线程可以独立改变副本,不会影响到其它线程。
这里假设多线程环境一个可能落显无聊的示范,初始化一个计数,然后循环输出:
@WebServlet("/threadLocalServlet")

    public class ThreadLocalServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    

    private static ThreadLocal threadLocal = new ThreadLocal() {

    @Override

    protected Integer initialValue() {

    return 0;

    }

    };

    

    protected void doGet(HttpServletRequest request,

    HttpServletResponse response) throws ServletException, IOException {

    response.setHeader("Connection", "Keep-Alive");

    

    PrintWriter out = response.getWriter();

    out.println("start... "  + " [" + Thread.currentThread() + "]");

    out.flush();

    

    for (int i = 0; i < 20; i++) {

    out.println(threadLocal.get());

    out.flush();

    

    threadLocal.set(threadLocal.get() + 1);

    }

    

    // 手动清理,当然随着当前线程结束,亦会自动清理调

    threadLocal.remove();

    out.println("finish... ");

    out.flush();

    out.close();

    }

    } 
若创建一个对象较为昂贵,但又是非线程安全的,在某种情况下要求仅仅需要在线程中独立变化,不会影响到其它线程。选择使用ThreadLocal较好一些,嗯,还有,其内部使用到了WeakHashMap,弱引用,当前线程结束,意味着创建的对象副本也会被垃圾回收。
Hibernate使用ThreadLocal创建Session;Spring亦用于创建对象会使用到一点。
嗯,请注意这不是解决多线程共享变量的钥匙,甚至你想让某个属性或对象在所有线程中都保持原子性,显然这不是解决方案。

4,

  1. Lock
    没什么好说的,现在JDK版本支持显式的加锁,相比synchronized,添加与释放更加灵活,功能更为全面。
    @WebServlet("/lockServlet")
    
        public class LockServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        
    
        private static int num = 0;
    
        private static final Lock lock = new ReentrantLock();
    
        
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        try{
    
        lock.lock();
    
        num ++;
    
        response.getWriter().println(num);
    
        }finally{
    
        lock.unlock();
    
        }
    
        }
    
        }
    
        
    必须手动释放锁,否则将会一直锁定。
  2. wait/notify
    较老的线程线程同步方案,较之Lock,不建议再次使用。
  3. 原子操作
    原子包装类,包括一些基本类型(int, long, double, boolean等)的包装,对象属性的包装等。
    @WebServlet("/atomicServlet")
    
        public class AtomicServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        private static final AtomicInteger num = new AtomicInteger(0);
    
        
    
        protected void doGet(HttpServletRequest request,
    
        HttpServletResponse response) throws ServletException, IOException {
    
        PrintWriter out = response.getWriter();
    
        out.println(num.incrementAndGet());
    
        out.flush();
    
        out.close();
    
        }
    
        }
    
        
    包装类提供了很多的快捷方法,比如上面的incrementAndGet方法,自身增加1,然后返回结果值,并且还是线程安全的,省缺了我们很多手动、笨拙的编码实现。
    更多原子类,请参见 java.util.concurrent.atomic包。
  4. 一些建议
    尽量不要在Servlet中单独启用线程
    使用尽可能使用局部变量
    尽可能避免使用锁
  5. 数据结构小结
    在平常环境下使用的一些数据结构,在多线程并发环境下可选择使用java.util.concurrent里内置替代品。下面有一个小结,仅供参考。

你可能感兴趣的:(servlet,线程)