1. 程式人生 > >怎麼實現springMVC 多執行緒併發

怎麼實現springMVC 多執行緒併發

我們知道Spring通過各種DAO模板類降低了開發者使用各種資料持久技術的難度。這些模板類都是執行緒安全的,也就是說,多個DAO可以複用同一個模板例項而不會發生衝突。
我們使用模板類訪問底層資料,根據持久化技術的不同,模板類需要繫結資料連線或會話的資源。但這些資源本身是非執行緒安全的,也就是說它們不能在同一時刻被多個執行緒共享。
雖然模板類通過資源池獲取資料連線或會話,但資源池本身解決的是資料連線或會話的快取問題,並非資料連線或會話的執行緒安全問題。
按照傳統經驗,如果某個物件是非執行緒安全的,在多執行緒環境下,對物件的訪問必須採用synchronized進行執行緒同步。但Spring的DAO模板類並未採用執行緒同步機制,因為執行緒同步限制了併發訪問,會帶來很大的效能損失。
此外,通過程式碼同步解決效能安全問題挑戰性很大,可能會增強好幾倍的實現難度。那模板類究竟仰丈何種魔法神功,可以在無需同步的情況下就化解執行緒安全的難題呢?答案就是ThreadLocal!
ThreadLocal在Spring中發揮著重要的作用,在管理request作用域的Bean、事務管理、任務排程、AOP等模組都出現了它們的身影,起著舉足輕重的作用。要想了解Spring事務管理的底層技術,ThreadLocal是必須攻克的山頭堡壘。
ThreadLocal是什麼
早在JDK1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。
ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地執行緒”。其實,ThreadLocal並不是一個Thread,而是Thread的區域性變數,也許把它命名為ThreadLocalVariable更容易讓人理解一些。
當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。
從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。
執行緒區域性變數並不是Java的新發明,很多語言(如IBM IBM XLFORTRAN)在語法層面就提供執行緒區域性變數。在Java中沒有提供在語言級支援,而是變相地通過ThreadLocal的類提供支援。
所以,在Java中編寫執行緒區域性變數的程式碼相對來說要笨拙一些,因此造成執行緒區域性變數沒有在Java開發者中得到很好的普及。
ThreadLocal的介面方法
ThreadLocal類介面很簡單,只有4個方法,我們先來了解一下:
void set(Object value)
設定當前執行緒的執行緒區域性變數的值。
public Object get()
該方法返回當前執行緒所對應的執行緒區域性變數。
public void remove()
將當前執行緒區域性變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK5.0新增的方法。需要指出的是,當執行緒結束後,對應該執行緒的區域性變數將自動被垃圾回收,所以顯式呼叫該方法清除執行緒的區域性變數並不是必須的操作,但它可以加快記憶體回收的速度。
protected Object initialValue()
返回該執行緒區域性變數的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲呼叫方法,線上程第1次呼叫get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的預設實現直接返回一個null。

值得一提的是,在JDK5.0中,ThreadLocal已經支援泛型,該類的類名已經變為ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是voidset(T value)、T get()以及T initialValue()。
ThreadLocal是如何做到為每一個執行緒維護變數的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值對應執行緒的變數副本。我們自己就可以提供一個簡單的實現版本:
// 程式碼清單1 SimpleThreadLocal
class SimpleThreadLocal {
    private MapvalueMap = Collections.synchronizedMap(new HashMap());
    public voidset(Object newValue) {
       valueMap.put(Thread.currentThread(), newValue);//①鍵為執行緒物件,值為本執行緒的變數副本
    }
    publicObject get() {
       Thread currentThread = Thread.currentThread();
       Object o = valueMap.get(currentThread);// ②返回本執行緒對應的變數
       if (o == null &&!valueMap.containsKey(currentThread)) {// ③如果在Map中不存在,放到Map
           // 中儲存起來。
           o = initialValue();
           valueMap.put(currentThread, o);
       }
       return o;
    }
    public voidremove() {
       valueMap.remove(Thread.currentThread());
    }
    publicObject initialValue() {
       return null;
    }
}

雖然程式碼清單9?3這個ThreadLocal實現版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現思路上是相近的。
一個TheadLocal例項
下面,我們通過一個具體的例項瞭解一下ThreadLocal的具體使用方法
package threadLocalDemo;
public class SequenceNumber {
    //①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    privatestatic ThreadLocal<Integer> seqNum =new ThreadLocal<Integer>() {
       public Integer initialValue() {
           return 0;
       }
    };
    //②獲取下一個序列值
    public intgetNextNum() {
       seqNum.set(seqNum.get() + 1);
       return seqNum.get();
    }
    publicstatic void main(String[] args)
    {
       SequenceNumber sn = new SequenceNumber();
       // ③ 3個執行緒共享sn,各自產生序列號
       TestClient t1 = new TestClient(sn);
       TestClient t2 = new TestClient(sn);
       TestClient t3 = new TestClient(sn);
       t1.start();
       t2.start();
       t3.start();
    }
    privatestatic class TestClient extends Thread
    {
       private SequenceNumber sn;
       public TestClient(SequenceNumber sn) {
           this.sn = sn;
       }
       public void run()
       {
           for (int i = 0; i < 3; i++) {
               // ④每個執行緒打出3個序列值
               System.out.println("thread[" + Thread.currentThread().getName()+"]sn[" + sn.getNextNum() + "]");
           }
       }
    }
}

通常我們通過匿名內部類的方式定義ThreadLocal的子類,提供初始的變數值,如例子中①處所示。TestClient執行緒產生一組序列號,在③處,我們生成3個TestClient,它們共享同一個SequenceNumber例項。執行以上程式碼,在控制檯上輸出以下的結果:
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]
考察輸出的結果資訊,我們發現每個執行緒所產生的序號雖然都共享同一個SequenceNumber例項,但它們並沒有發生相互干擾的情況,而是各自產生獨立的序列號,這是因為我們通過ThreadLocal為每一個執行緒提供了單獨的副本。
Thread同步機制的比較
ThreadLocal和執行緒同步機制相比有什麼優勢呢?ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。
在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。
由於ThreadLocal中可以持有任何型別的物件,低版本JDK所提供的get()返回的是Object物件,需要強制型別轉換。但JDK5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用,程式碼清單 9 2就使用了JDK5.0新的ThreadLocal<T>版本。
概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。
Spring使用ThreadLocal解決執行緒安全問題
我們知道在一般情況下,只有無狀態的Bean才可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以宣告為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非執行緒安全狀態採用ThreadLocal進行處理,讓它們也成為執行緒安全的狀態,因為有狀態的Bean就可以在多執行緒中共享了。
一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過介面向上層開放功能呼叫。在一般情況下,從接收請求到返回響應所經過的所有程式呼叫都同屬於一個執行緒,如圖9?2所示:

圖1同一執行緒貫通三層
這樣你就可以根據需要,將一些非執行緒安全的變數以ThreadLocal存放,在同一次請求響應的呼叫執行緒中,所有關聯的物件引用到的都是同一個變數。
下面的例項能夠體現Spring對有狀態Bean的改造思路:
程式碼清單3 TopicDao:非執行緒安全
public class TopicDao {
private Connection conn;①一個非執行緒安全的變數
public void addTopic(){
Statement stat = conn.createStatement();②引用非執行緒安全變數
…
}
}

由於①處的conn是成員變數,因為addTopic()方法是非執行緒安全的,必須在使用時建立一個新TopicDao例項(非singleton)。下面使用ThreadLocal對conn這個非執行緒安全的“狀態”進行改造:
程式碼清單4 TopicDao:執行緒安全
package threadLocalDemo;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class SqlConnection {
    //①使用ThreadLocal儲存Connection變數
    privatestatic ThreadLocal<Connection>connThreadLocal = newThreadLocal<Connection>();
    publicstatic Connection getConnection() {
       // ②如果connThreadLocal沒有本執行緒對應的Connection建立一個新的Connection,
       // 並將其儲存到執行緒本地變數中。
       if (connThreadLocal.get() == null) {
           Connection conn = getConnection();
           connThreadLocal.set(conn);
           return conn;
       } else {
           return connThreadLocal.get();
           // ③直接返回執行緒本地變數
       }
    }
    public voidaddTopic() {
       // ④從ThreadLocal中獲取執行緒對應的Connection
       try {
           Statement stat = getConnection().createStatement();
       } catch (SQLException e) {
           e.printStackTrace();
       }
    }
}

相關推薦

怎麼實現springMVC 執行併發

我們知道Spring通過各種DAO模板類降低了開發者使用各種資料持久技術的難度。這些模板類都是執行緒安全的,也就是說,多個DAO可以複用同一個模板例項而不會發生衝突。 我們使用模板類訪問底層資料,根據持久化技術的不同,模板類需要繫結資料連線或會話的資源。但這些資源本身是非執

3種方式實現python執行併發處理

標籤: python奇淫技巧 最優執行緒數 Ncpu=CPU的數量 Ucpu=目標CPU使用率 W/C=等待時間與計算時間的比率 為保持處理器達到期望的使用率,最優的執行緒池的大小等於$$Nthreads=Ncpu*Ucpu*(1+W/C$$ cpu密集型任務,即$W<

乾貨!執行池+CountDownLatch,實現 執行併發計算、彙總

目錄結構 抽象類:求和器 單執行緒 求和器 VS 多執行緒 求和器 1)執行緒池 多個執行緒 一起併發執行,效能很生猛 2)CountDownLatch 主執行緒 使用 latch.await() 阻塞住,直到所有 子任務 都執行完畢了

使用GroboUtils執行併發請求測試springmvc controller

1. 需求 有一個下載pdf的springmvc controller,希望模擬一下併發訪問的情況,怎麼辦呢? 2. 解決方案 由於是pdf,其http響應為 那麼我們訪問時返回的是二進位制,而不是網頁那種text/html 於是寫一個儲存二進位制檔案的方

OkHttp實現執行併發下載的筆記

         打個廣告,不瞭解OkHttp的話可以先看下  http://blog.csdn.net/brycegao321/article/details/51830525             需求:  手機拍攝若干張照片, 在wifi連線下上傳到伺服器。  

Callable+ThreadPoolExecutor實現執行併發並獲得返回值

前言 經常會遇到一些效能問題,比如呼叫某個介面,可能要迴圈呼叫100次,並且需要拿到每一次呼叫的返回結果,通常我們都是放在for迴圈中一次次的序列呼叫,這種方式可想而知道有多慢,那怎麼解決這個問題呢? 多執行緒 為了解決以上問題,我使用的方式是多執行緒。多

Java執行/併發20、Future實現類:FutureTask

FutureTask是future的實現類,它同時實現了兩個介面:Runnable和Future,所以它既可以作為Runnable被執行緒執行,又可以作為Future得到Callable的返回值。 因此我們可以: - 呼叫FutureTask物件的ru

python 實現執行併發執行 【join函式】

主執行緒啟動一個子執行緒t並等到t執行緒結束後才執行: import threading import time def reading(): for i in range(5): print("reading", i) time.

執行併發下的單例模式實現

1.1 天生執行緒安全的餓漢式單例 1.2 懶漢式單例 1.2.1 執行緒不安全的懶漢式單例 1.2.2 執行緒安全的懶漢式單例

藉助shared_ptr實現copy-on-write以提高執行併發效能

        鎖競爭是伺服器效能四大殺手之一(其他三個是:資料拷貝、環境切換、動態資源申請),本文將基於之前釋出的kimgbo網路I/O庫,以一個多執行緒群發聊天伺服器的實現為例,介紹如何藉助shared_ptr提高多執行緒併發的效能。         多執行緒群發聊天伺

Windows下基於socket執行併發通訊的實現

    本文介紹了在Windows 作業系統下基於TCP/IP 協議Socket 套介面的通訊機制以及多執行緒程式設計知識與技巧,並給出多執行緒方式實現多使用者與服務端(C/S)併發通訊模型的詳細演算法,最後展現了用C++編寫的多使用者與伺服器通訊的應用例項並附有程式。 關

Spring--springmvc執行池Executor做執行併發操作

載入xml檔案在ApplicationContext.xml檔案裡面新增[java] view plain copy print?xmlns:task="http://www.springframework.org/schema/task"xmlns:task="http

java 執行併發系列之 生產者消費者模式的兩種實現

生產者消費者模式是併發、多執行緒程式設計中經典的設計模式,生產者和消費者通過分離的執行工作解耦,簡化了開發模式,生產者和消費者可以以不同的速度生產和消費資料。真實世界中的生產者消費者模式生產者和消費者模式在生活當中隨處可見,它描述的是協調與協作的關係。比如一個人正在準備食物(

boost中asio網路庫執行併發處理實現,以及asio在執行模型中執行的排程情況和執行安全。

1、實現多執行緒方法: 其實就是多個執行緒同時呼叫io_service::run         for (int i = 0; i != m_nThreads; ++i)         {             boost::shared_ptr<boost::

Java執行併發07——鎖在Java中的實現

> 上一篇文章中,我們已經介紹過了各種鎖,讓各位對鎖有了一定的瞭解。接下來將為各位介紹鎖在Java中的實現。關注我的公眾號「Java面典」瞭解更多 Java 相關知識點。 在 Java 中主要通過使用synchronized 、 volatile關鍵字,及 Lock 介面的子類 ReentrantLock

執行併發如何高效實現生產者/消費者?

前言 無需引入第三方訊息佇列元件,我們如何利用內建C#語法高效實現生產者/消費者對資料進行處理呢?在.NET Core共享框架(Share Framework)引入了通道(Channel),也就是說無需額外通過NuGet包安裝,若為.NET Framework則需通過NuGet安裝,前提是版本必須是4.6+(

【小家Java】Future、FutureTask、CompletionService、CompletableFuture解決執行併發中歸集問題的效率對比

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

面試題之——執行併發面試題

1) 什麼是執行緒?   執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程序之中,是程序中的實際運作單位。程式設計師可以通過它進行多處理器程式設計,你可以使用多執行緒對運算密集型任務提速。比如,如果一個執行緒完成一個任務要100毫秒,那麼用十個執行緒完成改任務只需10毫秒。Java在語言層面對多執行

網際網路架構執行併發程式設計高階教程(上)

#基礎篇幅:執行緒基礎知識、併發安全性、JDK鎖相關知識、執行緒間的通訊機制、JDK提供的原子類、併發容器、執行緒池相關知識點  #高階篇幅:ReentrantLock原始碼分析、對比兩者原始碼,更加深入理解讀寫鎖,JAVA記憶體模型、先行發生原則、指令重排序 #環境說明:idea、ja

實驗5 結果不唯一的執行併發執行例項 操作指導

實驗5  結果不唯一的多執行緒併發執行例項 操作指導 變數及函式說明   pthread_t 型別定義:typedef unsigned long int pthread_t; //come from /usr/include/bits/