发送命令时是直接操作RedisOutputStream写字节。
在多线程环境下复用Jedis对象,其实就是在复用RedisOutputStream。如果多个线程在执行操作,那么既无法确保整条命令以一个原子操作写入Socket,也无法确保写入后、读取前没有其他数据写到远端。
这就能解释了为何多线程下使用Jedis对象操作Redis会出现各种问题:
- 写操作互相干扰,多条命令交织,必然是非法的Redis命令,则Redis会关闭客户端连接,导致连接断开
- 线程1和2先后写入get a和get b请求,Redis也返回了值1和2,但是线程2先读取了数据1就会出现数据错乱的问题。
那么如何修复呢?
使用Jedis提供的线程安全的类JedisPool来获得Jedis的实例。JedisPool作为连接池,可以声明为static 被多线程共享。注意使用try-with-resources模式。
这样使用后代码不再有线程安全问题。最好再通过shutdownhook,在程序退出之前关闭JedisPool:
//
@PostConstruct public void init() { Runtime.getRuntime().addShutdownHook(new Thread(() -> { jedisPool.close(); })); }
Jedis#close
若Jedis是从连接池获取的话,则close方法会调用连接池的return方法归还连接:
如果不是,则直接关闭连接,其最终调用Connection类的disconnect方法来关闭TCP连接:
可见Jedis可独立使用,也可配合连接池(JedisPool)
JedisPool
- JedisPool继承JedisPoolAbstract又继承抽象类Pool,Pool内部持有Apache Common的GenericObjectPool。
所以JedisPool的连接池其实就是直接复用的GenericObjectPool,并没有自己实现一套池子。
综上,Jedis API属于连接池和连接分离的API,JedisPool是线程安全的连接池,Jedis是非线程安全的单一连接。