聶永的博客

          記錄工作/學習的點點滴滴。

          Servlet 3.0筆記之Servlet單實例以及線程安全小結

          眾所周知,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、多線程情況下保證數據線程同步的幾個方法。

          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
            可以保證每一個線程都可以獨享一份變量副本,每個線程可以獨立改變副本,不會影響到其它線程。
            這里假設多線程環境一個可能落顯無聊的示范,初始化一個計數,然后循環輸出:
            @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. 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();
            }
            }
            }
            必須手動釋放鎖,否則將會一直鎖定。
          5. wait/notify
            較老的線程線程同步方案,較之Lock,不建議再次使用。
          6. 原子操作
            原子包裝類,包括一些基本類型(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包。
          7. 一些建議
            盡量不要在Servlet中單獨啟用線程
            使用盡可能使用局部變量
            盡可能避免使用鎖
          8. 數據結構小結
            在平常環境下使用的一些數據結構,在多線程并發環境下可選擇使用java.util.concurrent里內置替代品。下面有一個小結,僅供參考。
          非線程安全工具版JUC版本
          HashMap Collections.synchronizedMap ConcurrentHashMap
          ArrayList Collections.synchronizedList CopyOnWriteArrayList
          HashSet Collections.synchronizedSet synchronizedSet
          Queue   ConcurrentLinkedQueue



          Servlet線程安全有很太多的話題要說,以上僅僅為蜻蜓點水,真正需要學習和實踐的還有一段長路要學習。另,話說,這里已和Servlet版本無關,是知識積累和分享。

          posted on 2011-03-14 13:17 nieyong 閱讀(6439) 評論(4)  編輯  收藏 所屬分類: Servlet3

          評論

          # re: Servlet 3.0筆記之Servlet單實例以及線程安全小結[未登錄] 2011-03-15 09:18 Jarod

          眾所周知servlet是線程安全的,線程不安全的是你自己的狀態數據,與servlet完全無關  回復  更多評論   

          # re: Servlet 3.0筆記之Servlet單實例以及線程安全小結 2012-03-01 19:58 1111

          AnnotationServlet.doGet,Servlet: /AnnotationServlet servlet3.AnnotationServlet@5acb5e96
          AnnotationServlet.doGet,Servlet: /annotationServlet servlet3.AnnotationServlet@5acb5e96

          我的測試是不同URL,是同一個Servlet處理的哦..

          @WebServlet(urlPatterns ={"/annotationServlet","/AnnotationServlet"})
          public class AnnotationServlet extends HttpServlet
          {
          ...
          }  回復  更多評論   

          # re: Servlet 3.0筆記之Servlet單實例以及線程安全小結 2013-01-06 22:12 zelin

          /printServlet1[com.huawei.servlet.PrintServlet@136c1da]
          /printServlet1[com.huawei.servlet.PrintServlet@136c1da]
          朋友,我的輸出結果和你的不同啊。
          我的測試是不用URL,創建同一個Servlet。  回復  更多評論   

          # re: Servlet 3.0筆記之Servlet單實例以及線程安全小結 2015-08-20 10:17 浩哥

          應該是單實例!樓主能不能將環境配置搞出來  回復  更多評論   

          公告

          所有文章皆為原創,若轉載請標明出處,謝謝~

          新浪微博,歡迎關注:

          導航

          <2011年3月>
          272812345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 玛曲县| 绩溪县| 昌平区| 富川| 嘉鱼县| 大埔县| 佛冈县| 新龙县| 萝北县| 承德市| 璧山县| 福建省| 黄陵县| 民县| 达拉特旗| 黄龙县| 沛县| 宕昌县| 图们市| 类乌齐县| 浮梁县| 丹棱县| 深泽县| 内乡县| 曲松县| 合作市| 达拉特旗| 义乌市| 晋江市| 姜堰市| 怀宁县| 冀州市| 凉山| 岑巩县| 渝中区| 福州市| 泰州市| 安西县| 全州县| 左权县| 墨脱县|