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组件,能够有效地减小花在处理对象池化上的工做量,进而,向其它重要的工做里,投入更多的时间和精力。