mvn deploy:deploy-file -DgroupId=org.csource.fastdfs -DartifactId=fastdfs-client -Dversion=1.24 -Dpackaging=jar -Dfile=D:\\fastdfs-client-1.24.jar -Durl=http://172.16.6.214:8081/nexus/content/groups/public/ -DrepositoryId=nexus
JAR創(chuàng)建到本地
少年阿賓那些青春的歲月 |
JAR上傳到NEXUS
mvn deploy:deploy-file -DgroupId=org.csource.fastdfs -DartifactId=fastdfs-client -Dversion=1.24 -Dpackaging=jar -Dfile=D:\\fastdfs-client-1.24.jar -Durl=http://172.16.6.214:8081/nexus/content/groups/public/ -DrepositoryId=nexus JAR創(chuàng)建到本地 mvn install:install-file -DgroupId=com.home.link -DartifactId=fastdfs-client -Dversion=1.24 -Dpackaging=jar -Dfile=D:\\fastdfs-client-1.24.jar RAID 是“獨立磁盤冗余陣列”(最初為“廉價磁盤冗余陣列”)的縮略語,1987 年由Patterson, Gibson 和Katz 在加州大學伯克利分院的一篇文章中定義。RAID 陣列技術(shù)允許將一系列磁盤分組,以實現(xiàn)提高可用性的目的,并提供為實現(xiàn)數(shù)據(jù)保護而必需的數(shù)據(jù)冗余,有時還有改善性能的作用。我們將對七個RAID 級別: 0,1,3,5,10,30 和50 作些說明。最前面的4 個級別0,1,3,5,)已被定為工業(yè)標準,10 級、30 級和50 級則反應了ACCSTOR2000 磁盤陣列可以提供的功能。了解每個級別的特征將有助于您判斷哪個級別最適合您的需要,本文的最后一部分將提供一份指導方針,幫助您選擇最適合您需要的RAID 級別。RAID 級別可以通過軟件或硬件實現(xiàn)。許多但不是全部網(wǎng)絡操作系統(tǒng)支持的RAID 級別至少要達到5 級,RAID10、30和50 在ACCSTOR2000 磁盤陣列控制下才能實現(xiàn)。基于軟件的RAID 需要使用主機CPU 周期和系統(tǒng)內(nèi)存,從而增加了系統(tǒng)開銷,直接影響系統(tǒng)的性能。磁盤陣列控制器把RAID 的計算和操縱工作由軟件移到了專門的硬件上,一般比軟件實現(xiàn)RAID 的系統(tǒng)性能要好。
JDK中的實現(xiàn) 在JDK中LinkedHashMap可以作為LRU算法以及插入順序的實現(xiàn),LinkedHashMap繼承自HashMap,底層結(jié)合hash表和雙向鏈表,元素的插入和查詢等操作通過計算hash值找到其數(shù)組位置,在做插入或則查詢操作是,將元素插入到鏈表的表頭(當然得先刪除鏈表中的老元素),如果容量滿了,則刪除LRU這個元素,在鏈表表尾的元素即是。 LinkedHashMap的時間復雜度和HashMap差不多,雙向鏈表的刪除和表頭插入等操作都是O(1)復雜度,故不會影響HashMap的操作性能,插入,查詢,刪除LRU元素等操作均是O(1)的時間復雜度。 LinkedHashMap不是線程安全的類,用于多線程緩存則需要花點心思去同步它了,JDK中有支持并發(fā)的高性能ConcurrenHashMap,沒有ConcurrenListHashMap的實現(xiàn),故要使用支持并發(fā)的高性能LRU算法還得靠自己去繼承ConcurrenHashMap或則其他方式實現(xiàn)。Google以及其它資深研發(fā)團隊已有ConcurrenListHashMap的實現(xiàn) 當然知道了原理,我們自己也可以來實現(xiàn)LRU算法 1.簡單的計數(shù)或則LU時間標記法 底層使用hash表,插入和訪問的時間都可以做到O(1)復雜度,容量滿了需要刪除LRU,則需要遍歷一遍數(shù)組,通過計數(shù)或則LU時間得到LRU,刪除之,時間復雜度O(n)。此算法簡單容易實現(xiàn),適合數(shù)據(jù)量不大的需求 2.通過棧或雙向鏈表 這種算法通過維護元素的在鏈表中的順序來達到計算元素的訪問熱度,不需要額外的空間來計數(shù)或則記錄訪問時間。 插入時,先檢查改元素存在此鏈表中沒有,有的刪除之,然后再將元素插入表頭。 訪問時,遍歷數(shù)組查找該元素,然后將該元素移動至表頭。 這樣一來,最近被訪問的元素都在表頭,故要找出LRU則只需要刪除表尾元素即可。插入和訪問的時間復雜度均為O(n),如果用來作為緩存(查詢操作頻繁),相比hash表的實現(xiàn),性能相當不好啊 3.結(jié)合HashMap和雙向鏈表 通過上面可知道HashMap可實現(xiàn)讀寫O(1)復雜度,但是找出LRU需要遍歷整個數(shù)組,而通過維護鏈表則相反,僅需要O(1)就可以找出LRU,故將兩者結(jié)合起來,實現(xiàn)綜合復雜度為O(1)的LRU算法 使用數(shù)組來存放元素,插入時通過hash計算出其位置,然后改變該元素在鏈表中的指針,兩個操作的時間復雜度均為1。具體實現(xiàn)可參考JDK的LinkedHashMap http://www.360doc.com/content/14/0402/09/10504424_365635496.shtml Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現(xiàn)這個機制的協(xié)議叫做Zab協(xié)議。Zab協(xié)議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啟動或者在領導者崩潰后,Zab就進入了恢復模式,當領導者被選舉出來,且大多數(shù)Server完成了和leader的狀態(tài)同步以后,恢復模式就結(jié)束了。狀態(tài)同步保證了leader和Server具有相同的系統(tǒng)狀態(tài)。 為了保證事務的順序一致性,zookeeper采用了遞增的事務id號(zxid)來標識事務。所有的提議(proposal)都在被提出的時候加上了zxid。實現(xiàn)中zxid是一個64位的數(shù)字,它高32位是epoch用來標識leader關系是否改變,每次一個leader被選出來,它都會有一個新的epoch,標識當前屬于那個leader的統(tǒng)治時期。低32位用于遞增計數(shù)。 每個Server在工作過程中有三種狀態(tài): LOOKING:當前Server不知道leader是誰,正在搜尋 LEADING:當前Server即為選舉出來的leader FOLLOWING:leader已經(jīng)選舉出來,當前Server與之同步 Leader選舉流程: 當leader崩潰或者leader失去大多數(shù)的follower,這時候zk進入恢復模式,恢復模式需要重新選舉出一個新的leader,讓所有的Server都恢復到一個正確的狀態(tài)。Zk的選舉算法有兩種:一種是基于basic paxos實現(xiàn)的,另外一種是基于fast paxos算法實現(xiàn)的。系統(tǒng)默認的選舉算法為fast paxos。先介紹basic paxos流程: 1 .選舉線程由當前Server發(fā)起選舉的線程擔任,其主要功能是對投票結(jié)果進行統(tǒng)計,并選出推薦的Server; 2 .選舉線程首先向所有Server發(fā)起一次詢問(包括自己); 3 .選舉線程收到回復后,驗證是否是自己發(fā)起的詢問(驗證zxid是否一致),然后獲取對方的id(myid),并存儲到當前詢問對象列表中,最后獲取對方提議的leader相關信息( id,zxid),并將這些信息存儲到當次選舉的投票記錄表中; 4. 收到所有Server回復以后,就計算出zxid最大的那個Server,并將這個Server相關信息設置成下一次要投票的Server; 5. 線程將當前zxid最大的Server設置為當前Server要推薦的Leader,如果此時獲勝的Server獲得n/2 + 1的Server票數(shù), 設置當前推薦的leader為獲勝的Server,將根據(jù)獲勝的Server相關信息設置自己的狀態(tài),否則,繼續(xù)這個過程,直到leader被選舉出來。 通過流程分析我們可以得出:要使Leader獲得多數(shù)Server的支持,則Server總數(shù)必須是奇數(shù)2n+1,且存活的Server的數(shù)目不得少于n+1. 每個Server啟動后都會重復以上流程。在恢復模式下,如果是剛從崩潰狀態(tài)恢復的或者剛啟動的server還會從磁盤快照中恢復數(shù)據(jù)和會話信息,zk會記錄事務日志并定期進行快照,方便在恢復時進行狀態(tài)恢復。 fast paxos流程是在選舉過程中,某Server首先向所有Server提議自己要成為leader,當其它Server收到提議以后,解決epoch和zxid的沖突,并接受對方的提議,然后向?qū)Ψ桨l(fā)送接受提議完成的消息,重復這個流程,最后一定能選舉出Leader。 zookeeper數(shù)據(jù)同步過程: 選完leader以后,zk就進入狀態(tài)同步過程。 1. leader等待server連接; 2 .Follower連接leader,將最大的zxid發(fā)送給leader; 3 .Leader根據(jù)follower的zxid確定同步點; 4 .完成同步后通知follower 已經(jīng)成為uptodate狀態(tài); 5 .Follower收到uptodate消息后,又可以重新接受client的請求進行服務了。 工作流程 Leader工作流程 Leader主要有三個功能: 1 .恢復數(shù)據(jù); 2 .維持與Learner的心跳,接收Learner請求并判斷Learner的請求消息類型; 3 .Learner的消息類型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根據(jù)不同的消息類型,進行不同的處理。 PING消息是指Learner的心跳信息;REQUEST消息是Follower發(fā)送的提議信息,包括寫請求及同步請求;ACK消息是Follower的對提議的回復,超過半數(shù)的Follower通過,則commit該提議;REVALIDATE消息是用來延長SESSION有效時間。Leader的工作流程簡圖如下所示,在實際實現(xiàn)中,流程要比下圖復雜得多,啟動了三個線程來實現(xiàn)功能。 2.3.2 Follower工作流程 Follower主要有四個功能: 1. 向Leader發(fā)送請求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息); 2 .接收Leader消息并進行處理; 3 .接收Client的請求,如果為寫請求,發(fā)送給Leader進行投票; 4 .返回Client結(jié)果。 Follower的消息循環(huán)處理如下幾種來自Leader的消息: 1 .PING消息:心跳消息; 2 .PROPOSAL消息:Leader發(fā)起的提案,要求Follower投票; 3 .COMMIT消息:服務器端最新一次提案的信息; 4 .UPTODATE消息:表明同步完成; 5 .REVALIDATE消息:根據(jù)Leader的REVALIDATE結(jié)果,關閉待revalidate的session還是允許其接受消息; 6 .SYNC消息:返回SYNC結(jié)果到客戶端,這個消息最初由客戶端發(fā)起,用來強制得到最新的更新。 Follower的工作流程簡圖如下所示,在實際實現(xiàn)中,F(xiàn)ollower是通過5個線程來實現(xiàn)功能的。 http://www.it165.net/admin/html/201405/2997.html 靜態(tài)代理 靜態(tài)代理相對來說比較簡單,無非就是聚合+多態(tài): 參考:設計模式筆記 – Proxy 代理模式 (Design Pattern) 動態(tài)代理 我們知道,通過使用代理,可以在被代理的類的方法的前后添加一些處理方法,這樣就達到了類似AOP的效果。而JDK中提供的動態(tài)代理,就是實現(xiàn)AOP的絕好底層技術(shù)。 JDK動態(tài)代理 JDK動態(tài)代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler。InvocationHandler是一個接口,通過實現(xiàn)該接口定義橫切邏輯,并通過反射機制調(diào)用目標類的代碼,動態(tài)將橫切邏輯和業(yè)務邏輯編制在一起。 Proxy利用InvocationHandler動態(tài)創(chuàng)建一個符合某一接口的實例,生成目標類的代理對象。 例子:Java筆記 – 反射 動態(tài)代理 CGLib動態(tài)代理 還有一個叫CGLib的動態(tài)代理,CGLib全稱為Code Generation Library,是一個強大的高性能,高質(zhì)量的代碼生成類庫,可以在運行期擴展Java類與實現(xiàn)Java接口,CGLib封裝了asm,可以再運行期動態(tài)生成新的class。和JDK動態(tài)代理相比較:JDK創(chuàng)建代理有一個限制,就是只能為接口創(chuàng)建代理實例,而對于沒有通過接口定義業(yè)務方法的類,則可以通過CGLib創(chuàng)建動態(tài)代理。 CGLib采用非常底層的字節(jié)碼技術(shù),可以為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,并順勢織入橫切邏輯。 JDK動態(tài)代理和CGLib的比較 CGLib所創(chuàng)建的動態(tài)代理對象的性能比JDK所創(chuàng)建的代理對象性能高不少,大概10倍,但CGLib在創(chuàng)建代理對象時所花費的時間卻比JDK動態(tài)代理多大概8倍,所以對于singleton的代理對象或者具有實例池的代理,因為無需頻繁的創(chuàng)建新的實例,所以比較適合CGLib動態(tài)代理技術(shù),反之則適用于JDK動態(tài)代理技術(shù)。另外,由于CGLib采用動態(tài)創(chuàng)建子類的方式生成代理對象,所以不能對目標類中的final,private等方法進行處理。所以,大家需要根據(jù)實際的情況選擇使用什么樣的代理了。 同樣的,Spring的AOP編程中相關的ProxyFactory代理工廠內(nèi)部就是使用JDK動態(tài)代理或CGLib動態(tài)代理的,通過動態(tài)代理,將增強(advice)應用到目標類中。 JDK動態(tài)代理主要用到java.lang.reflect包中的兩個類:Proxy和InvocationHandler. InvocationHandler是一個接口,通過實現(xiàn)該接口定義橫切邏輯,并通過反射機制調(diào)用目標類的代碼,動態(tài)的將橫切邏輯和業(yè)務邏輯編織在一起。 Proxy利用InvocationHandler動態(tài)創(chuàng)建一個符合某一接口的實例,生成目標類的代理對象。 Cookie攻擊:
防止Cookie被抓包了之后攻擊: 就是驗證的東西盡量多就行了,比如里面添加IP驗證,添加一些MAC地址,添加有效期,添加 NIO通常采用Reactor模式,AIO通常采用Proactor模式。AIO簡化了程序的編寫,stream的讀取和寫入都有OS來完成,不需 要像NIO那樣子遍歷Selector。Windows基于IOCP實現(xiàn)AIO,Linux只有eppoll模擬實現(xiàn)了AIO。 基于原生nio的socket通信時一種很好的解決方案,基于事件的通知模式使得多并發(fā)時不用維持高數(shù)量的線程,高并發(fā)的socket服務器的java實現(xiàn)成為現(xiàn)實。不過原生nio代碼十分復雜,無論編寫還是修改都是一件頭疼的事。“屏蔽底層的繁瑣工作,讓程序員將注意力集中于業(yè)務邏輯本身”,有需求就有生產(chǎn)力進步, 狀態(tài)模式(state pattern)和策略模式(strategy pattern)的實現(xiàn)方法非常類似,都是利用多態(tài)把一些操作分配到一組相關的簡單的類中,因此很多人認為這兩種模式實際上是相同的。然而 •在現(xiàn)實世界中,策略(如促銷一種商品的策略)和狀態(tài)(如同一個按鈕來控制一個電梯的狀態(tài),又如手機界面中一個按鈕來控制手機)是兩種完全不同的思想。當我們對狀態(tài)和策略進行建模時,這種差異會導致完全不同的問題。例如,對狀態(tài)進行建模時,狀態(tài)遷移是一個核心內(nèi)容;然而,在選擇策略時,遷移與此毫無關系。另外,策略模式允許一個客戶選擇或提供一種策略,而這種思想在狀態(tài)模式中完全沒有。 •一個策略是一個計劃或方案,通過執(zhí)行這個計劃或方案,我們可以在給定的輸入條件下達到一個特定的目標。策略是一組方案,他們可以相互替換;選擇一個策略,獲得策略的輸出。策略模式用于隨不同外部環(huán)境采取不同行為的場合。我們可以參考微軟企業(yè)庫底層Object Builder的創(chuàng)建對象的strategy實現(xiàn)方式。 •而狀態(tài)模式不同,對一個狀態(tài)特別重要的對象,通過狀態(tài)機來建模一個對象的狀態(tài);狀態(tài)模式處理的核心問題是狀態(tài)的遷移,因為在對象存在很多狀態(tài)情況下,對各個business flow,各個狀態(tài)之間跳轉(zhuǎn)和遷移過程都是及其復雜的。例如一個工作流,審批一個文件,存在新建、提交、已修改、HR部門審批中、老板審批中、HR審批失敗、老板審批失敗等狀態(tài),涉及多個角色交互,涉及很多事件,這種情況下用狀態(tài)模式(狀態(tài)機)來建模更加合適;把各個狀態(tài)和相應的實現(xiàn)步驟封裝成一組簡單的繼承自一個接口或抽象類的類,通過另外的一個Context來操作他們之間的自動狀態(tài)變換,通過event來自動實現(xiàn)各個狀態(tài)之間的跳轉(zhuǎn)。在整個生命周期中存在一個狀態(tài)的遷移曲線,這個遷移曲線對客戶是透明的。我們可以參考微軟最新的WWF 狀態(tài)機工作流實現(xiàn)思想。 •在狀態(tài)模式中,狀態(tài)的變遷是由對象的內(nèi)部條件決定,外界只需關心其接口,不必關心其狀態(tài)對象的創(chuàng)建和轉(zhuǎn)化;而策略模式里,采取何種策略由外部條件(C)決定。
Spring什么時候?qū)嵗痓ean,首先要分2種情況
第一:如果你使用BeanFactory作為Spring Bean的工廠類,則所有的bean都是在第一次使用該Bean的時候?qū)嵗?nbsp; 第二:如果你使用ApplicationContext作為Spring Bean的工廠類,則又分為以下幾種情況: (1):如果bean的scope是singleton的,并且lazy-init為false(默認是false,所以可以不用設置),則ApplicationContext啟動的時候就實例化該Bean,并且將實例化的Bean放在一個map結(jié)構(gòu)的緩存中,下次再使用該Bean的時候,直接從這個緩存中取 (2):如果bean的scope是singleton的,并且lazy-init為true,則該Bean的實例化是在第一次使用該Bean的時候進行實例化 (3):如果bean的scope是prototype的,則該Bean的實例化是在第一次使用該Bean的時候進行實例化 1、lazy init 在getBean時實例化 2、非lazy的單例bean 容器初始化時實例化 3、prototype等 getBean時實例化
<!--applicationContext.xml配置:--> <bean id="personService" class="cn.mytest.service.impl.PersonServiceBean"></bean> id是對象的名稱,class是要實例化的類,然后再通過正常的方式進調(diào)用實例化的類即可,比如: public void instanceSpring(){ //加載spring配置文件 ApplicationContext ac = new ClassPathXmlApplicationContext( new String[]{ "/conf/applicationContext.xml" }); //調(diào)用getBean方法取得被實例化的對象。 PersonServiceBean psb = (PersonServiceBean) ac.getBean("personService"); psb.save(); } 采用這種實例化方式要注意的是:要實例化的類中如果有構(gòu)造器的話,一定要有一個無參的構(gòu)造器。
二、使用靜態(tài)工廠方法實例化; 根據(jù)這個中實例化方法的名稱就可以知道要想通過這種方式進行實例化就要具備兩個條件:(一)、要有工廠類及其工廠方法;(二)、工廠方法是靜態(tài)的。OK,知道這兩點就好辦了,首先創(chuàng)建工程類及其靜態(tài)方法: package cn.mytest.service.impl; /** *創(chuàng)建工廠類 * */ public class PersonServiceFactory { //創(chuàng)建靜態(tài)方法 public static PersonServiceBean createPersonServiceBean(){ //返回實例化的類的對象 return new PersonServiceBean(); } } 然后再去配置spring配置文件,配置的方法和上面有點不同,這里也是關鍵所在<!--applicationContext.xml配置:--> <bean id="personService1" class="cn.mytest.service.impl.PersonServiceFactory" factory-method="createPersonServiceBean"></bean> id是實例化的對象的名稱,class是工廠類,也就實現(xiàn)實例化類的靜態(tài)方法所屬的類,factory-method是實現(xiàn)實例化類的靜態(tài)方法。 然后按照正常的調(diào)用方法去調(diào)用即可: public void instanceSpring(){ //加載spring配置文件 ApplicationContext ac = new ClassPathXmlApplicationContext( new String[]{ "/conf/applicationContext.xml" }); //調(diào)用getBean方法取得被實例化的對象。 PersonServiceBean psb = (PersonServiceBean) ac.getBean("personService1"); psb.save(); } 三、使用實例化工廠方法實例化。 這個方法和上面的方法不同之處在與使用該實例化方式工廠方法不需要是靜態(tài)的,但是在spring的配置文件中需要配置更多的內(nèi)容,,首先創(chuàng)建工廠類及工廠方法: package cn.mytest.service.impl; /** *創(chuàng)建工廠類 * */ public class PersonServiceFactory { //創(chuàng)建靜態(tài)方法 public PersonServiceBean createPersonServiceBean1(){ //返回實例化的類的對象 return new PersonServiceBean(); } } 然后再去配置spring配置文件,配置的方法和上面有點不同,這里也是關鍵所在<!--applicationContext.xml配置:--> <bean id="personServiceFactory" class="cn.mytest.service.impl.PersonServiceFactory"></bean> <bean id="personService2" factory-bean="personServiceFactory" factory-method="createPersonServiceBean1"></bean> 這里需要配置兩個bean,第一個bean使用的構(gòu)造器方法實例化工廠類,第二個bean中的id是實例化對象的名稱,factory-bean對應的被實例化的工廠類的對象名稱,也就是第一個bean的id,factory-method是非靜態(tài)工廠方法。
然后按照正常的調(diào)用方法去調(diào)用即可: public void instanceSpring(){ //加載spring配置文件 ApplicationContext ac = new ClassPathXmlApplicationContext( new String[]{ "/conf/applicationContext.xml" }); //調(diào)用getBean方法取得被實例化的對象。 PersonServiceBean psb = (PersonServiceBean) ac.getBean("personService2"); psb.save(); }
所謂原子操作,就是"不可中斷的一個或一系列操作" 。
硬件級的原子操作: 在單處理器系統(tǒng)(UniProcessor)中,能夠在單條指令中完成的操作都可以認為是" 原子操作",因為中斷只能發(fā)生于指令之間。這也是某些CPU指令系統(tǒng)中引入了test_and_set、test_and_clear等指令用于臨界資源互斥的原因。 在對稱多處理器(Symmetric Multi-Processor)結(jié)構(gòu)中就不同了,由于系統(tǒng)中有多個處理器在獨立地運行,即使能在單條指令中完成的操作也有可能受到干擾。 在x86 平臺上,CPU提供了在指令執(zhí)行期間對總線加鎖的手段。CPU芯片上有一條引線#HLOCK pin,如果匯編語言的程序中在一條指令前面加上前綴"LOCK",經(jīng)過匯編以后的機器代碼就使CPU在執(zhí)行這條指令的時候把#HLOCK pin的電位拉低,持續(xù)到這條指令結(jié)束時放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時不能通過總線訪問內(nèi)存了,保證了這條指令在多處理器環(huán)境中的 原子性。 軟件級的原子操作: 軟件級的原子操作實現(xiàn)依賴于硬件原子操作的支持。 對于linux而言,內(nèi)核提供了兩組原子操作接口:一組是針對整數(shù)進行操作;另一組是針對單獨的位進行操作。 2.1. 原子整數(shù)操作 針對整數(shù)的原子操作只能對atomic_t類型的數(shù)據(jù)處理。這里沒有使用C語言的int類型,主要是因為: 1) 讓原子函數(shù)只接受atomic_t類型操作數(shù),可以確保原子操作只與這種特殊類型數(shù)據(jù)一起使用 2) 使用atomic_t類型確保編譯器不對相應的值進行訪問優(yōu)化 3) 使用atomic_t類型可以屏蔽不同體系結(jié)構(gòu)上的數(shù)據(jù)類型的差異。盡管Linux支持的所有機器上的整型數(shù)據(jù)都是32位,但是使用atomic_t的代碼只能將該類型的數(shù)據(jù)當作24位來使用。這個限制完全是因為在SPARC體系結(jié)構(gòu)上,原子操作的實現(xiàn)不同于其它體系結(jié)構(gòu):32位int類型的低8位嵌入了一個鎖,因為SPARC體系結(jié)構(gòu)對原子操作缺乏指令級的支持,所以只能利用該鎖來避免對原子類型數(shù)據(jù)的并發(fā)訪問。 原子整數(shù)操作最常見的用途就是實現(xiàn)計數(shù)器。原子整數(shù)操作列表在中定義。原子操作通常是內(nèi)斂函數(shù),往往通過內(nèi)嵌匯編指令來實現(xiàn)。如果某個函數(shù)本來就是原子的,那么它往往會被定義成一個宏。 在編寫內(nèi)核時,操作也簡單: atomic_t use_cnt; atomic_set(&use_cnt, 2); atomic_add(4, &use_cnt); atomic_inc(use_cnt); 2.2. 原子性與順序性 原子性確保指令執(zhí)行期間不被打斷,要么全部執(zhí)行,要么根本不執(zhí)行。而順序性確保即使兩條或多條指令出現(xiàn)在獨立的執(zhí)行線程中,甚至獨立的處理器上,它們本該執(zhí)行的順序依然要保持。 2.3. 原子位操作 原子位操作定義在文件中。令人感到奇怪的是位操作函數(shù)是對普通的內(nèi)存地址進行操作的。原子位操作在多數(shù)情況下是對一個字長的內(nèi)存訪問,因而位號該位于0-31之間(在64位機器上是0-63之間),但是對位號的范圍沒有限制。 編寫內(nèi)核代碼,只要把指向了你希望的數(shù)據(jù)的指針給操作函數(shù),就可以進行位操作了: unsigned long word = 0; set_bit(0, &word); /*第0位被設置*/ set_bit(1, &word); /*第1位被設置*/ clear_bit(1, &word); /*第1位被清空*/ change_bit(0, &word); /*翻轉(zhuǎn)第0位*/ 為什么關注原子操作? 1)在確認一個操作是原子的情況下,多線程環(huán)境里面,我們可以避免僅僅為保護這個操作在外圍加上性能開銷昂貴的鎖。 2)借助于原子操作,我們可以實現(xiàn)互斥鎖。 3)借助于互斥鎖,我們可以把一些列操作變?yōu)樵硬僮鳌?/span> GNU C中x++是原子操作嗎? 答案不是。x++由3條指令完成。x++在單CPU下不是原子操作。 對應3條匯編指令 movl x, %eax addl $1, %eax movl %eax, x 在vc2005下對應 ++x; 004232FA mov eax,dword ptr [x] 004232FD add eax,1 00423300 mov dword ptr [x],eax 仍然是3條指令。 所以++x,x++等都不是原子操作。因其步驟包括了從內(nèi)存中取x值放入寄存器,加寄存器,把值寫入內(nèi)存三個指令。 如何實現(xiàn)x++的原子性? 在單處理器上,如果執(zhí)行x++時,禁止多線程調(diào)度,就可以實現(xiàn)原子。因為單處理的多線程并發(fā)是偽并發(fā)。 在多處理器上,需要借助cpu提供的Lock功能。鎖總線。讀取內(nèi)存值,修改,寫回內(nèi)存三步期間禁止別的CPU訪問總線。同時我估計使用Lock指令鎖總線的時候,OS也不會把當前線程調(diào)度走了。要是調(diào)走了,那就麻煩了。 在多處理器系統(tǒng)中存在潛在問題的原因是: 不使用LOCK指令前綴鎖定總線的話,在一次內(nèi)存訪問周期中有可能其他處理器會產(chǎn)生異常或中斷,而在異常處理中有可能會修改尚未寫入的地址,這樣當INC操作完成后會產(chǎn)生無效數(shù)據(jù)(覆蓋了前面的修改)。 spinlock 用于CPU同步, 它的實現(xiàn)是基于CPU鎖定數(shù)據(jù)總線的指令. 當某個CPU鎖住數(shù)據(jù)總線后, 它讀一個內(nèi)存單元(spinlock_t)來判斷這個spinlock 是否已經(jīng)被別的CPU鎖住. 如果否, 它寫進一個特定值, 表示鎖定成功, 然后返回. 如果是, 它會重復以上操作直到成功, 或者spin次數(shù)超過一個設定值. 鎖定數(shù)據(jù)總線的指令只能保證一個機器指令內(nèi), CPU獨占數(shù)據(jù)總線. 單CPU當然能用spinlock, 但實現(xiàn)上無需鎖定數(shù)據(jù)總線. spinlock在鎖定的時候,如果不成功,不會睡眠,會持續(xù)的嘗試,單cpu的時候spinlock會讓其它process動不了. |