http://blog.sina.com.cn/s/blog_6aefe42501018wnn.html
1緩存為什么要存在?
2緩存可以存在于什么地方?
3緩存有哪些屬性?
4緩存介質(zhì)?
搞清楚這4個(gè)問(wèn)題,那么我們就可以隨意的通過(guò)應(yīng)用的場(chǎng)景來(lái)判斷使用何種緩存了.
1. 緩存為什么要存在?
一般情況下,一個(gè)網(wǎng)站,或者一個(gè)應(yīng)用,它的一般形式是,瀏覽器請(qǐng)求應(yīng)用服務(wù)器,應(yīng)用服務(wù)器做一堆計(jì)算后再請(qǐng)求數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)收到請(qǐng)求后再作一堆計(jì)算后把數(shù)據(jù)返回給應(yīng)用服務(wù)器,應(yīng)用服務(wù)器再作一堆計(jì)算后把數(shù)據(jù)返回給瀏覽器.這個(gè)是一個(gè)標(biāo)準(zhǔn)流程.但是隨著互連網(wǎng)的普及,上網(wǎng)的人越來(lái)越多,網(wǎng)上的信息量也越來(lái)越多,在這兩個(gè)越來(lái)越多的情況下,我們的應(yīng)用需要支撐的并發(fā)量就越來(lái)越多.然后我們的應(yīng)用服務(wù)器和數(shù)據(jù)庫(kù)服務(wù)器所做的計(jì)算也越來(lái)越多,但是往往我們的應(yīng)用服務(wù)器資源是有限的,數(shù)據(jù)庫(kù)每秒中接受請(qǐng)求的次數(shù)也是有限的(誰(shuí)叫俺們的硬盤(pán)轉(zhuǎn)速有限呢).如果利用有限的資源來(lái)提供盡可能大的吞吐量呢,一個(gè)辦法:減少計(jì)算量,縮短請(qǐng)求流程(減少網(wǎng)絡(luò)io或者硬盤(pán)io),這時(shí)候緩存就可以大展手腳了.緩存的基本原理就是打破上圖中所描繪的標(biāo)準(zhǔn)流程,在這個(gè)標(biāo)準(zhǔn)流程中,任何一個(gè)環(huán)節(jié)都可以被切斷.請(qǐng)求可以從緩存里取到數(shù)據(jù)直接返回.這樣不但節(jié)省了時(shí)間,提高了響應(yīng)速度,而且也節(jié)省了硬件資源.可以讓我們有限的硬件資源來(lái)服務(wù)更多的用戶(hù).
2 緩存可以存在于什么地方?
Java代碼
瀏覽器---?瀏覽器和app之間---?分過(guò)層的app-?數(shù)據(jù)庫(kù)
瀏覽器---?瀏覽器和app之間---?分過(guò)層的app-?數(shù)據(jù)庫(kù)
在上圖中,我們可以看到一次請(qǐng)求的一般流程,下面我們重新繪制這張圖,讓我們的結(jié)構(gòu)稍微復(fù)雜一點(diǎn)點(diǎn).
(將app分層)
瀏覽器---?瀏覽器和app之間---?分過(guò)層的app-?數(shù)據(jù)庫(kù)
理論上來(lái)將,請(qǐng)求的任何一個(gè)環(huán)節(jié)都是緩存可以作用的地方.第一個(gè)環(huán)節(jié),瀏覽器,如果數(shù)據(jù)存在瀏覽器上,那么對(duì)用戶(hù)來(lái)說(shuō)速度是最快的,因?yàn)檫@個(gè)時(shí)候根本無(wú)需網(wǎng)絡(luò)請(qǐng)求.第二個(gè)環(huán)節(jié),瀏覽器和app之間,如果緩存加在這個(gè)地方,那么緩存對(duì)app來(lái)說(shuō)是透明的.而且這個(gè)緩存中存放的是完整的頁(yè)面.第三個(gè)節(jié)點(diǎn),app 中本身就有幾個(gè)層次,那么緩存也可以放在不同的層次上,這一部分是情況或者場(chǎng)景比較復(fù)雜的部分.選擇緩存時(shí)需要謹(jǐn)慎.第四個(gè)環(huán)節(jié),數(shù)據(jù)庫(kù)中也可以有緩存, 比如說(shuō)mysql的querycache.
那么也就是說(shuō)在整個(gè)請(qǐng)求流程的任何一點(diǎn),我們都可以加緩存.但是是所有的數(shù)據(jù)都可以放進(jìn)緩存的嗎.當(dāng)然不是,需要放進(jìn)緩存的數(shù)據(jù)總是有一些特征的,要清楚的判斷數(shù)據(jù)是否可以被緩存,可以被怎樣緩存就必須要從數(shù)據(jù)的變化特征下手.
數(shù)據(jù)有哪些變化特征?最簡(jiǎn)單的就是兩種,變和不變.我們都知道,不會(huì)變化的數(shù)據(jù)不需要每次都進(jìn)行計(jì)算.問(wèn)題是難道所有的數(shù)據(jù)理論上來(lái)講都會(huì)變化,變化是世界永恒的主題.也就是說(shuō)我們把數(shù)據(jù)分為變和不變兩種是不對(duì)的,那么就讓我們?cè)偌右粋€(gè)條件:時(shí)間.那么我們就可以把數(shù)據(jù)特征總結(jié)為一段時(shí)間內(nèi)變或者不變.那么根據(jù)這個(gè)數(shù)據(jù)特征,我們就可以在合適的位置和合適的緩存類(lèi)型中緩存該數(shù)據(jù).
3緩存有哪些屬性
從面向?qū)ο蟮慕嵌葋?lái)看,緩存就是一個(gè)對(duì)象,那么是對(duì)象,必然有屬性.那么下面我們來(lái)探討一下緩存有哪些屬性.以下列舉我們常用到的3個(gè)屬性.
(1) 命中率
命中率是指請(qǐng)求緩存次數(shù)和緩存返回正確結(jié)果次數(shù)的比例.比例越高,就證明緩存的使用率越高.
命中率問(wèn)題是緩存中的一個(gè)非常重要的問(wèn)題,我們都希望自己緩存的命中率能達(dá)到100%,但是往往事與愿違,而且緩存命中率是衡量緩存有效性的重要指標(biāo).
(2) 最大元素
緩存中可以存放得最大元素得數(shù)量,一旦緩存中元素?cái)?shù)量超過(guò)這個(gè)值,那么將會(huì)起用緩存清空策略,根據(jù)不同的場(chǎng)景合理的設(shè)置最大元素值往往可以一定程度上提高緩存的命中率.從而更有效的時(shí)候緩存.
(3) 清空策略
1 FIFO ,first in first out ,最先進(jìn)入緩存得數(shù)據(jù)在緩存空間不夠情況下(超出最大元素限制時(shí))會(huì)被首先清理出去
2 LFU , Less Frequently Used ,一直以來(lái)最少被使用的元素會(huì)被被清理掉。這就要求緩存的元素有一個(gè)hit 屬性,在緩存空間不夠得情況下,hit 值最小的將會(huì)被清出緩存。
2 LRU ,Least Recently Used ,最近最少使用的,緩存的元素有一個(gè)時(shí)間戳,當(dāng)緩存容量滿(mǎn)了,而又需要騰出地方來(lái)緩存新的元素的時(shí)候,那么現(xiàn)有緩存元素中時(shí)間戳離當(dāng)前時(shí)間最遠(yuǎn)的元素將被清出緩存。
4緩存介質(zhì)
從硬件介質(zhì)上來(lái)將無(wú)非就是兩種,內(nèi)存和硬盤(pán)(對(duì)應(yīng)應(yīng)用層的程序來(lái)講不用考慮寄存器等問(wèn)題).但是往往我們不會(huì)從硬件上來(lái)劃分,一般的劃分方法是從技術(shù)上劃分,可以分成幾種,內(nèi)存,硬盤(pán)文件.數(shù)據(jù)庫(kù).
(1) 內(nèi)存.將緩存放在內(nèi)存中是最快的選擇,任何程序直接操作內(nèi)存都比操作硬盤(pán)要快的多,但是如果你的數(shù)據(jù)要考慮到break down的問(wèn)題,因?yàn)榉旁趦?nèi)存中的數(shù)據(jù)我們稱(chēng)之為沒(méi)有持久話(huà)的數(shù)據(jù),如果硬盤(pán)上沒(méi)有備份,機(jī)器down機(jī)之后,很難或者無(wú)法恢復(fù).
(2) 硬盤(pán).一般來(lái)說(shuō),很多緩存框架會(huì)結(jié)合使用內(nèi)存和硬盤(pán),比如給內(nèi)存分配的空間有滿(mǎn)了之后,會(huì)讓用戶(hù)選擇把需要退出內(nèi)存空間的數(shù)據(jù)持久化到硬盤(pán).當(dāng)然也選擇直接把數(shù)據(jù)放一份到硬盤(pán)(內(nèi)存中一份,硬盤(pán)中一份,down機(jī)也不怕).也有其他的緩存是直接把數(shù)據(jù)放到硬盤(pán)上.
(3) 數(shù)據(jù)庫(kù).說(shuō)到數(shù)據(jù)庫(kù),可能有的人會(huì)想,之前不是講到要減少數(shù)據(jù)庫(kù)查詢(xún)的次數(shù),減少數(shù)據(jù)庫(kù)計(jì)算的壓力嗎,現(xiàn)在怎么又用數(shù)據(jù)庫(kù)作為緩存的介質(zhì)了呢.這是因?yàn)閿?shù)據(jù)庫(kù)又很多種類(lèi)型,比如berkleydb,這種db不支持sql語(yǔ)句,沒(méi)有sql引擎,只是key和value的存儲(chǔ)結(jié)構(gòu),所以速度非常的快,在當(dāng)代一般的pc上,每秒中十幾w次查詢(xún)都是沒(méi)有問(wèn)題的(當(dāng)然這個(gè)是根據(jù)業(yè)務(wù)特征來(lái)決定的,如果您訪問(wèn)的數(shù)據(jù)在分布上是均勻的,那ahuaxuan可不能保證這個(gè)速度了).
除了緩存介質(zhì)之外,ahuaxuan根據(jù)緩存和應(yīng)用的耦合程度將其劃分為local cache和remote cache.
Local cache是指包含在應(yīng)用之中的緩存組件.而remote cache指和應(yīng)用解耦在應(yīng)用之外的緩存組件.典型的local cache有ehcache,oscache,而remote cache有大名鼎鼎的memcached.
Localcache 最大的優(yōu)點(diǎn)是應(yīng)用和cache的時(shí)候是在同一個(gè)進(jìn)程內(nèi)部,請(qǐng)求緩存非常快速,完全不需要網(wǎng)絡(luò)開(kāi)銷(xiāo)等.所以單應(yīng)用,不需要集群或者集群情況下cache node不需要相互通知的情況下使用local cache比較合適.這也是java中ehcache和oscache這么流行的原因.
但是 Local cache是有一定的缺點(diǎn)的,一般這種緩存框架(比如java中的ehcache或者oscache)都是local cache.也就是跟著應(yīng)用程序走的,多個(gè)應(yīng)用程序無(wú)法直接共享緩存,應(yīng)用集群的情況下這個(gè)問(wèn)題更加明顯,當(dāng)然也有的緩存組件提供了集群節(jié)點(diǎn)相互通知緩存更新的功能,但是由于這個(gè)是廣播,或者是環(huán)路更新,在緩存更新頻繁的情況下會(huì)導(dǎo)致網(wǎng)絡(luò)io開(kāi)銷(xiāo)非常大,嚴(yán)重的時(shí)候會(huì)影響應(yīng)用的正常運(yùn)行.而且如果緩存中數(shù)據(jù)量較大得情況下使用localcache意味著每個(gè)應(yīng)用都有一份這么大得緩存,著絕對(duì)是對(duì)內(nèi)存的浪費(fèi).
所以這個(gè)情況下,往往我們會(huì) 選擇remote cache,比如memcached.這樣集群或者分布式的情況下各個(gè)應(yīng)用都可以共享memcached中的數(shù)據(jù),這些應(yīng)用都通過(guò)socket和基于 tcp/ip協(xié)議上層的memcached協(xié)議直接連接到memcached,有一個(gè)app更新了memcached中的值,所有的應(yīng)用都能拿到最新的值.雖然這個(gè)時(shí)候多了很多了網(wǎng)絡(luò)上的開(kāi)銷(xiāo),但是往往這種方案要比localcache廣播或環(huán)路更新cache節(jié)點(diǎn)要普遍的多,而且性能也比后者高.由于數(shù)據(jù)只需要保存一份,所以也提高了內(nèi)存的使用率.
通過(guò)以上分析可以看出,不管是local cache,還是remote cache在緩存領(lǐng)域都有自己的一席之地,所以ahuaxuan建議在選擇或者使用緩存時(shí)一定要根據(jù)緩存的特征和我們的業(yè)務(wù)場(chǎng)景準(zhǔn)確判斷使用何種緩存.這樣才能充分發(fā)揮緩存的功能.
Ahuaxuan 認(rèn)為,緩存的使用是架構(gòu)師的必備技能,好的架構(gòu)師能夠根據(jù)數(shù)據(jù)的類(lèi)型,業(yè)務(wù)的場(chǎng)景來(lái)準(zhǔn)確的判斷出使用何種類(lèi)型的緩存,并且如何使用這種類(lèi)型的緩存.在緩存的世界里也沒(méi)有銀彈,目前還沒(méi)有一種緩存可以解決任何的業(yè)務(wù)場(chǎng)景或者數(shù)據(jù)類(lèi)型,如果這種技術(shù)出現(xiàn)了,那架構(gòu)師就又更不值錢(qián)了.呵呵.
OSCache
OSCache是個(gè)一個(gè)廣泛采用的高性能的J2EE緩存框架,OSCache能用于任何Java應(yīng)用程序的普通的緩存解決方案。
OSCache有以下特點(diǎn):
緩存任何對(duì)象,你可以不受限制的緩存部分jsp頁(yè)面或HTTP請(qǐng)求,任何java對(duì)象都可以緩存。
擁有全面的API--OSCache API給你全面的程序來(lái)控制所有的OSCache特性。
永久緩存--緩存能隨意的寫(xiě)入硬盤(pán),因此允許昂貴的創(chuàng)建(expensive-to-create)數(shù)據(jù)來(lái)保持緩存,甚至能讓?xiě)?yīng)用重啟。
支持集群--集群緩存數(shù)據(jù)能被單個(gè)的進(jìn)行參數(shù)配置,不需要修改代碼。
緩存記錄的過(guò)期--你可以有最大限度的控制緩存對(duì)象的過(guò)期,包括可插入式的刷新策略(如果默認(rèn)性能不需要時(shí))。
官方網(wǎng)站 http://www.opensymphony.com/oscache/
Java Caching System
JSC(Java Caching System)是一個(gè)用分布式的緩存系統(tǒng),是基于服務(wù)器的java應(yīng)用程序。它是通過(guò)提供管理各種動(dòng)態(tài)緩存數(shù)據(jù)來(lái)加速動(dòng)態(tài)web應(yīng)用。
JCS和其他緩存系統(tǒng)一樣,也是一個(gè)用于高速讀取,低速寫(xiě)入的應(yīng)用程序。
動(dòng)態(tài)內(nèi)容和報(bào)表系統(tǒng)能夠獲得更好的性能。
如果一個(gè)網(wǎng)站,有重復(fù)的網(wǎng)站結(jié)構(gòu),使用間歇性更新方式的數(shù)據(jù)庫(kù)(而不是連續(xù)不斷的更新數(shù)據(jù)庫(kù)),被重復(fù)搜索出相同結(jié)果的,就能夠通過(guò)執(zhí)行緩存方式改進(jìn)其性能和伸縮性。
官方網(wǎng)站 http://jakarta.apache.org/turbine/jcs/
EHCache
EHCache 是一個(gè)純java的在進(jìn)程中的緩存,它具有以下特性:快速,簡(jiǎn)單,為Hibernate2.1充當(dāng)可插入的緩存,最小的依賴(lài)性,全面的文檔和測(cè)試。
官方網(wǎng)站 http://ehcache.sourceforge.net/
JCache
JCache是個(gè)開(kāi)源程序,正在努力成為JSR-107開(kāi)源規(guī)范,JSR-107規(guī)范已經(jīng)很多年沒(méi)改變了。這個(gè)版本仍然是構(gòu)建在最初的功能定義上。
官方網(wǎng)站 http://jcache.sourceforge.net/
ShiftOne
ShiftOne Java Object Cache是一個(gè)執(zhí)行一系列嚴(yán)格的對(duì)象緩存策略的Java lib,就像一個(gè)輕量級(jí)的配置緩存工作狀態(tài)的框架。
官方網(wǎng)站 http://jocache.sourceforge.net/
SwarmCache
SwarmCache是一個(gè)簡(jiǎn)單且有效的分布式緩存,它使用IP multicast與同一個(gè)局域網(wǎng)的其他主機(jī)進(jìn)行通訊,是特別為集群和數(shù)據(jù)驅(qū)動(dòng)web應(yīng)用程序而設(shè)計(jì)的。SwarmCache能夠讓典型的讀操作大大超過(guò)寫(xiě)操作的這類(lèi)應(yīng)用提供更好的性能支持。
SwarmCache使用JavaGroups來(lái)管理從屬關(guān)系和分布式緩存的通訊。
官方網(wǎng)站 http://swarmcache.sourceforge.net
TreeCache / JBossCache
JBossCache是一個(gè)復(fù)制的事務(wù)處理緩存,它允許你緩存企業(yè)級(jí)應(yīng)用數(shù)據(jù)來(lái)更好的改善性能。緩存數(shù)據(jù)被自動(dòng)復(fù)制,讓你輕松進(jìn)行JBoss服務(wù)器之間的集群工作。JBossCache能夠通過(guò)JBoss應(yīng)用服務(wù)或其他J2EE容器來(lái)運(yùn)行一個(gè)MBean服務(wù),當(dāng)然,它也能獨(dú)立運(yùn)行。
JBossCache包括兩個(gè)模塊:TreeCache和TreeCacheAOP。
TreeCache --是一個(gè)樹(shù)形結(jié)構(gòu)復(fù)制的事務(wù)處理緩存。
TreeCacheAOP --是一個(gè)“面向?qū)ο?#8221;緩存,它使用AOP來(lái)動(dòng)態(tài)管理POJO(Plain Old Java Objects)
注:AOP是OOP的延續(xù),是Aspect Oriented Programming的縮寫(xiě),意思是面向方面編程。
官方網(wǎng)站 http://www.jboss.org/products/jbosscache
WhirlyCache
Whirlycache是一個(gè)快速的、可配置的、存在于內(nèi)存中的對(duì)象的緩存。它能夠通過(guò)緩存對(duì)象來(lái)加快網(wǎng)站或應(yīng)用程序的速度,否則就必須通過(guò)查詢(xún)數(shù)據(jù)庫(kù)或其他代價(jià)較高的處理程序來(lái)建立。
首先你要理解OOP的思想,是
面向接口編程.
什么叫
面向接口編程呢?
假如你買(mǎi)了一個(gè)
多媒體設(shè)備,它給了你一個(gè)遙控,你想要知道的只是按什么按鈕,它會(huì)播放什么
而遙控里面是怎樣運(yùn)行,還有屏幕里面怎么工作,你想知道嗎?
你完全不會(huì)去想了解.
那如果
多媒體設(shè)備需要更新,比如優(yōu)化內(nèi)部運(yùn)行效率,
但是優(yōu)化完了,遙控的按鈕不變,設(shè)備的所有操作方式都不變,按這個(gè)按鈕還是顯示相同的東西
那內(nèi)部怎么變化你完全不需要在意.
這就是
面向接口編程.
無(wú)論類(lèi)的內(nèi)部怎么實(shí)現(xiàn),它對(duì)外的接口不變,那它的使用方式就不會(huì)變
假設(shè)Main類(lèi)要使用D類(lèi)的一個(gè)draw的方法,
方法名叫 draw():void
不管draw里面是怎樣的,Main類(lèi)里就是這樣用,
那么你就從這個(gè)接口出發(fā),里面怎么實(shí)現(xiàn)是D類(lèi)的事了,Main類(lèi)只關(guān)心怎么用而已.
其他類(lèi)要使用它,還是相同
這就大大減少了維護(hù)的成本.
因?yàn)槿绻鸇類(lèi)出問(wèn)題,Main類(lèi)是完全不用改變的.
從上觀察,公開(kāi)的接口越多,維護(hù)成本就越大.
維護(hù)就越麻煩.所以我們先寫(xiě)接口,定死了公開(kāi)的接口,
那維護(hù)就很方便,出錯(cuò)也只是一個(gè)類(lèi)的事,而不用同時(shí)修改多個(gè)協(xié)同類(lèi)
http://www.cnblogs.com/doit8791/p/4093808.html
Spring框架里的bean,或者說(shuō)組件,獲取實(shí)例的時(shí)候都是默認(rèn)的單例模式,這是在多線程開(kāi)發(fā)的時(shí)候要尤其注意的地方。
http://www.cnblogs.com/hoojo/archive/2011/05/05/2038101.html
單例模式的意思就是只有一個(gè)實(shí)例。單例模式確保某一個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類(lèi)稱(chēng)為單例類(lèi)。
當(dāng)多用戶(hù)同時(shí)請(qǐng)求一個(gè)服務(wù)時(shí),容器會(huì)給每一個(gè)請(qǐng)求分配一個(gè)線程,這是多個(gè)線程會(huì)并發(fā)執(zhí)行該請(qǐng)求多對(duì)應(yīng)的業(yè)務(wù)邏輯(成員方法),此時(shí)就要注意了,如果該處理邏輯中有對(duì)該單列狀態(tài)的修改(體現(xiàn)為該單列的成員屬性),則必須考慮線程同步問(wèn)題
同步機(jī)制的比較 ThreadLocal和線程同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal和線程同步機(jī)制都是為了解決多線程中相同變量的訪問(wèn)沖突問(wèn)題。
在同步機(jī)制中,通過(guò)對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問(wèn)變量。這時(shí)該變量是多個(gè)線程共享的,使用同步機(jī)制要求程序慎密地分析什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫(xiě),什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放對(duì)象鎖等繁雜的問(wèn)題,程序設(shè)計(jì)和編寫(xiě)難度相對(duì)較大。
而ThreadLocal則從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問(wèn)。ThreadLocal會(huì)為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)數(shù)據(jù)的訪問(wèn)沖突。因?yàn)槊恳粋€(gè)線程都擁有自己的變量副本,從而也就沒(méi)有必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線程安全的共享對(duì)象,在編寫(xiě)多線程代碼時(shí),可以把不安全的變量封裝進(jìn)ThreadLocal。
由于ThreadLocal中可以持有任何類(lèi)型的對(duì)象,低版本JDK所提供的get()返回的是Object對(duì)象,需要強(qiáng)制類(lèi)型轉(zhuǎn)換。但JDK 5.0通過(guò)泛型很好的解決了這個(gè)問(wèn)題,在一定程度地簡(jiǎn)化ThreadLocal的使用
概括起來(lái)說(shuō),對(duì)于多線程資源共享的問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響。
Spring使用ThreadLocal解決線程安全問(wèn)題
我們知道在一般情況下,只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進(jìn)行處理,讓它們也成為線程安全的狀態(tài),因?yàn)橛袪顟B(tài)的Bean就可以在多線程中共享了。
一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個(gè)層次,在不同的層中編寫(xiě)對(duì)應(yīng)的邏輯,下層通過(guò)接口向上層開(kāi)放功能調(diào)用。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過(guò)的所有程序調(diào)用都同屬于一個(gè)線程
ThreadLocal是解決線程安全問(wèn)題一個(gè)很好的思路,它通過(guò)為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問(wèn)的沖突問(wèn)題。在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單,更方便,且結(jié)果程序擁有更高的并發(fā)性。
如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。 或者說(shuō):一個(gè)類(lèi)或者程序所提供的接口對(duì)于線程來(lái)說(shuō)是原子操作或者多個(gè)線程之間的切換不會(huì)導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說(shuō)我們不用考慮同步的問(wèn)題。 線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。
若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無(wú)寫(xiě)操作,一般來(lái)說(shuō),這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作,一般都需要考慮線程同步,否則就可能影響線程安全。
1) 常量始終是線程安全的,因?yàn)橹淮嬖谧x操作。
2)每次調(diào)用方法前都新建一個(gè)實(shí)例是線程安全的,因?yàn)椴粫?huì)訪問(wèn)共享的資源。
3)局部變量是線程安全的。因?yàn)槊繄?zhí)行一個(gè)方法,都會(huì)在獨(dú)立的空間創(chuàng)建局部變量,它不是共享的資源。局部變量包括方法的參數(shù)變量和方法內(nèi)變量。
有狀態(tài)就是有數(shù)據(jù)存儲(chǔ)功能。有狀態(tài)對(duì)象(Stateful Bean),就是有實(shí)例變量的對(duì)象 ,可以保存數(shù)據(jù),是非線程安全的。在不同方法調(diào)用間不保留任何狀態(tài)。
無(wú)狀態(tài)就是一次操作,不能保存數(shù)據(jù)。無(wú)狀態(tài)對(duì)象(Stateless Bean),就是沒(méi)有實(shí)例變量的對(duì)象 .不能保存數(shù)據(jù),是不變類(lèi),是線程安全的。
有狀態(tài)對(duì)象:
無(wú)狀態(tài)的Bean適合用不變模式,技術(shù)就是單例模式,這樣可以共享實(shí)例,提高性能。有狀態(tài)的Bean,多線程環(huán)境下不安全,那么適合用Prototype原型模式。Prototype: 每次對(duì)bean的請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。
Struts2默認(rèn)的實(shí)現(xiàn)是Prototype模式。也就是每個(gè)請(qǐng)求都新生成一個(gè)Action實(shí)例,所以不存在線程安全問(wèn)題。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。
二、線程安全案例:
SimpleDateFormat(下面簡(jiǎn)稱(chēng)sdf)類(lèi)內(nèi)部有一個(gè)Calendar對(duì)象引用,它用來(lái)儲(chǔ)存和這個(gè)sdf相關(guān)的日期信息,例如sdf.parse(dateStr), sdf.format(date) 諸如此類(lèi)的方法參數(shù)傳入的日期相關(guān)String, Date等等, 都是交友Calendar引用來(lái)儲(chǔ)存的.這樣就會(huì)導(dǎo)致一個(gè)問(wèn)題,如果你的sdf是個(gè)static的, 那么多個(gè)thread 之間就會(huì)共享這個(gè)sdf, 同時(shí)也是共享這個(gè)Calendar引用, 并且, 觀察 sdf.parse() 方法,你會(huì)發(fā)現(xiàn)有如下的調(diào)用:
Date parse() {
calendar.clear(); // 清理calendar
... // 執(zhí)行一些操作, 設(shè)置 calendar 的日期什么的
calendar.getTime(); // 獲取calendar的時(shí)間
}
這里會(huì)導(dǎo)致的問(wèn)題就是, 如果 線程A 調(diào)用了 sdf.parse(), 并且進(jìn)行了 calendar.clear()后還未執(zhí)行calendar.getTime()的時(shí)候,線程B又調(diào)用了sdf.parse(), 這時(shí)候線程B也執(zhí)行了sdf.clear()方法, 這樣就導(dǎo)致線程A的的calendar數(shù)據(jù)被清空了(實(shí)際上A,B的同時(shí)被清空了). 又或者當(dāng) A 執(zhí)行了calendar.clear() 后被掛起, 這時(shí)候B 開(kāi)始調(diào)用sdf.parse()并順利i結(jié)束, 這樣 A 的 calendar內(nèi)存儲(chǔ)的的date 變成了后來(lái)B設(shè)置的calendar的date
這個(gè)問(wèn)題背后隱藏著一個(gè)更為重要的問(wèn)題--無(wú)狀態(tài):無(wú)狀態(tài)方法的好處之一,就是它在各種環(huán)境下,都可以安全的調(diào)用。衡量一個(gè)方法是否是有狀態(tài)的,就看它是否改動(dòng)了其它的東西,比如全局變量,比如實(shí)例的字段。format方法在運(yùn)行過(guò)程中改動(dòng)了SimpleDateFormat的calendar字段,所以,它是有狀態(tài)的。
這也同時(shí)提醒我們?cè)陂_(kāi)發(fā)和設(shè)計(jì)系統(tǒng)的時(shí)候注意下一下三點(diǎn):
1.自己寫(xiě)公用類(lèi)的時(shí)候,要對(duì)多線程調(diào)用情況下的后果在注釋里進(jìn)行明確說(shuō)明
2.對(duì)線程環(huán)境下,對(duì)每一個(gè)共享的可變變量都要注意其線程安全性
3.我們的類(lèi)和方法在做設(shè)計(jì)的時(shí)候,要盡量設(shè)計(jì)成無(wú)狀態(tài)的
三.解決辦法
1.需要的時(shí)候創(chuàng)建新實(shí)例:
說(shuō)明:在需要用到SimpleDateFormat 的地方新建一個(gè)實(shí)例,不管什么時(shí)候,將有線程安全問(wèn)題的對(duì)象由共享變?yōu)榫植克接卸寄鼙苊舛嗑€程問(wèn)題,不過(guò)也加重了創(chuàng)建對(duì)象的負(fù)擔(dān)。在一般情況下,這樣其實(shí)對(duì)性能影響比不是很明顯的。
2.使用同步:同步SimpleDateFormat對(duì)象
public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}
說(shuō)明:當(dāng)線程較多時(shí),當(dāng)一個(gè)線程調(diào)用該方法時(shí),其他想要調(diào)用此方法的線程就要block,多線程并發(fā)量大的時(shí)候會(huì)對(duì)性能有一定的影響。
3.使用ThreadLocal:
public class ConcurrentDateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
}
或
public class ThreadLocalDateUtil {
private static final String date_format = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat()
{
DateFormat df = threadLocal.get();
if(df==null){
df = new SimpleDateFormat(date_format);
threadLocal.set(df);
}
return df;
}
public static String formatDate(Date date) throws ParseException {
return getDateFormat().format(date);
}
public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
}
說(shuō)明:使用ThreadLocal, 也是將共享變量變?yōu)楠?dú)享,線程獨(dú)享肯定能比方法獨(dú)享在并發(fā)環(huán)境中能減少不少創(chuàng)建對(duì)象的開(kāi)銷(xiāo)。如果對(duì)性能要求比較高的情況下,一般推薦使用這種方法。
4.拋棄JDK,使用其他類(lèi)庫(kù)中的時(shí)間格式化類(lèi):
1.使用Apache commons 里的FastDateFormat,宣稱(chēng)是既快又線程安全的SimpleDateFormat, 可惜它只能對(duì)日期進(jìn)行format, 不能對(duì)日期串進(jìn)行解析。
2.使用Joda-Time類(lèi)庫(kù)來(lái)處理時(shí)間相關(guān)問(wèn)題
做一個(gè)簡(jiǎn)單的壓力測(cè)試,方法一最慢,方法三最快,但是就算是最慢的方法一性能也不差,一般系統(tǒng)方法一和方法二就可以滿(mǎn)足,所以說(shuō)在這個(gè)點(diǎn)很難成為你系統(tǒng)的瓶頸所在。從簡(jiǎn)單的角度來(lái)說(shuō),建議使用方法一或者方法二,如果在必要的時(shí)候,追求那么一點(diǎn)性能提升的話(huà),可以考慮用方法三,用ThreadLocal做緩存。
Joda-Time類(lèi)庫(kù)對(duì)時(shí)間處理方式比較完美,建議使用。
http://www.cnblogs.com/hoojo/archive/2011/05/05/2038101.html
package comz.autoupdatefile;
import java.util.Timer;
import java.util.TimerTask;
public class M {
public static void main(String[] args) {
// TODO todo.generated by zoer
Timer timer = new Timer();
timer.schedule(new MyTask(), 1000, 2000);
}
}
class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("dddd");
}
}
這樣,就可以在1秒鐘之后開(kāi)始執(zhí)行mytask,每?jī)擅腌妶?zhí)行一次。
當(dāng)然,timer的功能也可以通過(guò)自己構(gòu)造線程,然后在線程中用sleep來(lái)模擬停止一段時(shí)間,然后再執(zhí)行某個(gè)動(dòng)作。
其實(shí),看一下timertask的源碼就立即可以知道,timertask就是實(shí)現(xiàn)了runnable接口的。也就是說(shuō),通過(guò)timer來(lái)間隔一段時(shí)間執(zhí)行一個(gè)操作,也是通過(guò)一個(gè)線程來(lái)做到的。