redis的併發競爭問題如何解決?java
Redis爲單進程單線程模式,採用隊列模式將併發訪問變爲串行訪問。Redis自己沒有鎖的概念,Redis對於多個客戶端鏈接並不存在競爭,可是在Jedis客戶端對Redis進行併發訪問時會發生鏈接超時、數據轉換錯誤、阻塞、客戶端關閉鏈接等問題,這些問題均是因爲客戶端鏈接混亂形成。對此有2種解決方法:
1.客戶端角度,爲保證每一個客戶端間正常有序與Redis進行通訊,對鏈接進行池化,同時對客戶端讀寫Redis操做採用內部鎖synchronized。
2.服務器角度,利用setnx實現鎖。
對於第一種,須要應用程序本身處理資源的同步,可使用的方法比較通俗,可使用synchronized也可使用lock;redis
這裏我要提到的是jedis的池化,即jedisPoolapache
對象的池化技術:服務器
咱們都知道一個對象好比car(對象)在其生命週期大體可氛圍"建立","使用","銷燬"三大階段,那麼它的總時間就是T1(建立)+T2(使用)+T3(銷燬),若是建立N個對象都須要這樣的步驟的話是很是耗性能的,就算JVM對垃圾回收機制有優化,但"建立"和"銷燬"多少總會佔用部分資源,那麼咱們就會想可否像常量池那樣,讓對象可複用,從而減小T1和T3所消耗的資源呢?這就引出了咱們今天的內容-----對象池化技術即ObjectPool (jedis也是一個Object對象,咱們下面先介紹對象池)併發
官網對對象池的解釋是:dom
將用過的對象保存起來,等下次須要這種對象的時候再拿出來重複使用,從而在必定程度上減小頻繁建立對象所形成的開銷,用於充當保存對象的"容器"對象,被稱爲"對象池"。性能
對於沒有狀態的對象,String,在重複使用以前,無需進行任何處理,對於有狀態的對象如StringBuffer,在重複使用以前,須要恢復到同等於生成的狀態,若是恢復不了的話,就只能丟掉,改用建立的實例了。並不是全部對象均可以用來池化,由於維護池也須要開銷,對生成時開銷不大的對象進行池化,它可能下降性能。優化
在上述的對對象池的描述來看,在看源碼以前我問了本身一個問題,若是讓你來作對象池你覺的應該注意什麼?spa
咱們舉個例子,所謂對象池嘛確定是管理對象的,咱們將對象當作是一臺公共自行車,將對象池比做一個自行車站點。那麼咱們來想象着,咱們怎麼能夠設計對象池線程
首先對於這個站點的功能咱們猜測,確定有"借"自行車,"還"自行車,按期"檢查"自行車車況,"銷燬"(壞了的,拿去修或者銷燬)這幾個基本的功能。
那對於自行車呢?站點確定不能本身造自行車,確定須要一個自行車工廠去維護自行車,那麼天然咱們想一想這個工廠會有哪些功能
首先做爲工廠,那麼確定會有"生產"功能,"檢測"功能,"出庫"功能,"銷燬"功能。
有了上述的假象以後,咱們帶着這些概念,看看真正的代碼是怎麼設計的,是否是跟我想的有點相似,下面咱們就來看源代碼
PooledObjectFactory、ObjectPool和ObjectPoolFactory
在Pool組件中,對象池化的工做被劃分給了三類對象:
相應地,使用Pool組件的過程,也大致能夠劃分紅「創立PooledObjectFactory」、「使用ObjectPool」和可選的「利用ObjectPoolFactory」三步。
PooledObjectFactory
是一個接口,由於工程能夠多多種工廠,自行車,客車,卡車等等。該接口jdk沒有默認實現,須要本身實現。點開對象的工廠,我驚奇的發現,徹底驗證了個人猜測
只是在基礎功能的基礎上,多增長了一些其餘的功能罷了。
import org.apache.commons.pool.PoolableObjectFactory; // 1) 建立一個實現了PoolableObjectFactory接口的類。 public class PoolableObjectFactorySample implements PoolableObjectFactory { private static int counter = 0; // 2) 爲這個類添加一個Object makeObject()方法。這個方法用於在必要時產生新的對象。 public Object makeObject() throws Exception { Object obj = String.valueOf(counter++); System.err.println("Making Object " + obj); return obj; } // 3) 爲這個類添加一個void activateObject(Object obj)方法。這個方法用於將對象「激活」——設置爲適合開始使用的狀態。 public void activateObject(Object obj) throws Exception { System.err.println("Activating Object " + obj); } // 4) 爲這個類添加一個void passivateObject(Object obj)方法。這個方法用於將對象「掛起」——設置爲適合開始休眠的狀態。 public void passivateObject(Object obj) throws Exception { System.err.println("Passivating Object " + obj); } // 5) 爲這個類添加一個boolean validateObject(Object obj)方法。這個方法用於校驗一個具體的對象是否仍然有效,已失效的對象會被自動交給destroyObject方法銷燬。 public boolean validateObject(Object obj) { /* 以1/2的機率將對象斷定爲失效 */ boolean result = (Math.random() > 0.5); System.err.println("Validating Object " + obj + " : " + result); return result; } // 6) 爲這個類添加一個void destroyObject(Object obj)方法。這個方法用於銷燬被validateObject斷定爲已失效的對象。 public void destroyObject(Object obj) throws Exception { System.err.println("Destroying Object " + obj); } }
ObjectPool
有了合適的PooledObjectFactory以後,即可以開始請出ObjectPool來與之同臺演出了。
ObjectPool是在org.apache.commons.pool包中定義的一個接口,實際使用的時候也須要利用這個接口的一個具體實現。Pool組件自己包含了若干種現成的ObjectPool實現,
StackObjectPool :
StackObjectPool利用一個java.util.Stack對象來保存對象池裏的對象。這種對象池的特點是:
SoftReferenceObjectPool
SoftReferenceObjectPool利用一個java.util.ArrayList對象來保存對象池裏的對象。不過它並不在對象池裏直接保存對象自己,而是保存它們的「軟引用」(Soft Reference)。這種對象池的特點是:
GenericObjectPool----jedis就是基於這個的
GenericObjectPool利用一個org.apache.commons.collections.CursorableLinkedList對象來保存對象池裏的對象。這種對象池的特點是:
能夠直接利用。若是都不合用,也能夠根據狀況自行建立。具體的建立方法,能夠參看Pool組件的文檔和源碼。
我這邊總結了下,對代碼作了一個簡化的修改
咱們能夠看到,這個對象池就跟咱們以前猜測的站點的功能同樣,主要的方法就是一個借borrowObject(),還returnObject()功能。
import org.apache.commons.pool.ObjectPool; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.StackObjectPool; public class ObjectPoolSample { public static void main(String[] args) { Object obj = null; // 1) 生成一個要用的PoolableObjectFactory類的實例。 PoolableObjectFactory factory = new PoolableObjectFactorySample(); // 2) 利用這個PoolableObjectFactory實例爲參數,生成一個實現了ObjectPool接口的類(例如StackObjectPool)的實例,做爲對象池。 ObjectPool pool = new StackObjectPool(factory); try { for(long i = 0; i < 100 ; i++) { System.out.println("== " + i + " =="); // 3) 須要從對象池中取出對象時,調用該對象池的Object borrowObject()方法。 obj = pool.borrowObject(); System.out.println(obj); // 4) 須要將對象放回對象池中時,調用該對象池的void returnObject(Object obj)方法。 pool.returnObject(obj); } obj = null;//明確地設爲null,做爲對象已歸還的標誌 } catch (Exception e) { e.printStackTrace(); } finally { try{ if (obj != null) {//避免將一個對象歸還兩次 pool.returnObject(obj); } // 5) 當再也不須要使用一個對象池時,調用該對象池的void close()方法,釋放它所佔據的資源。 pool.close(); } catch (Exception e){ e.printStackTrace(); } } } }
ObjectPoolFactory
有時候,要在多處生成類型和設置都相同的ObjectPool。若是在每一個地方都重寫一次調用相應構造方法的代碼,不但比較麻煩,並且往後修改起來,也有所不便。這種時候,正是使用ObjectPoolFactory的時機。
ObjectPoolFactory是一個在org.apache.commons.pool中定義的接口,它定義了一個稱爲ObjectPool createPool()方法,能夠用於大量生產類型和設置都相同的ObjectPool。
public static void main(String[] args) { Object obj = null; PoolableObjectFactory factory = new PoolableObjectFactorySample(); ObjectPoolFactory poolFactory = new StackObjectPoolFactory(factory); ObjectPool pool = poolFactory.createPool(); try { for(long i = 0; i < 100 ; i++) { System.out.println("== " + i + " =="); obj = pool.borrowObject(); System.out.println(obj); pool.returnObject(obj); } obj = null; } catch (Exception e) { e.printStackTrace(); } finally { try{ if (obj != null) { pool.returnObject(obj); } pool.close(); } catch (Exception e){ e.printStackTrace(); } } }
結束語 恰當地使用對象池化,能夠有效地下降頻繁生成某些對象所形成的開銷,從而提升總體的性能。而藉助Apache Commons Pool組件,能夠有效地減小花在處理對象池化上的工做量,進而,向其它重要的工做裏,投入更多的時間和精力。