在实际的工作中,有部分的特定场景需要使用到分布式锁来进行跨服务器资源的统一调配。之前在一家医疗互联网公司,因为黄牛抢号等原因,造成同一个患者同一时段在同一个医生处,挂到了两个及以上的号,我对之前我司实现的代码进行了封装和改进,在github上提供了源码,有需要的朋友,可以下载代码,并用maven打包成jar包,使用起来比较方便。

源码地址https://github.com/mantuliu/distributed

核心feature:

redis的setnx()方法,此方法提供了一个原子操作,可以保证有且只有一个分布式的调用返回值为1,在分布式锁的概念里,则代表此锁被此次调用的线程占用;

redis的expire()方法,通过此方法来设置此锁的过期时间;

对于死锁的情况,封装的分布式锁包可以自动解锁;

每次获取锁的线程,都会被标识,当此线程还没有释放锁时,此线程继续调用trylock方法,还可以获得该锁,只有获取该锁的线程才有unlock()释放锁的权利;

支持trylock()和trylock(long timeout, TimeUnit unit)两种方式;

使用应用服务器提供的jedis实例;

对于当前服务器试图获取该锁的线程数量进行监控,当数量大于阀值时,后续线程在trylock(long timeout, TimeUnit unit)及trylock()时,直接返回失败,阀值可以设置;

锁的过期时间默认值是5秒,可以根据实际情况进行设置;

锁的键值前缀默认值是mantu:dislock:,可以根据实际情况进行设置;

通过LockSupport类来获取锁,使得试图获取同一把锁的线程得到的对象是同一个。

源码解析:

   CommonType类

public class CommonType {

    public static int WAITLOCKERS = 2;//当前服务器等待锁的线程数量,如果超过或等于此值,当前线程直接返回,不再等待锁
public static String REDISKEY="mantu:dislock:";//redis下key前缀
public static int LOCKEXPIRETIME = 5;//锁的过期时间,单位秒,默认5秒过期
}

DisLock接口

public interface DisLock{

    boolean tryLock(Jedis jedis);
boolean tryLock(long time, TimeUnit unit,Jedis jedis) throws InterruptedException;
void unlock(Jedis jedis);
}

RedisDisLock类,实际的管理锁的类

public class RedisDisLock implements DisLock{
private static final Logger LOG = LoggerFactory.getLogger(RedisDisLock.class);
private transient Thread exclusiveOwnerThread; String lockKey="";
AtomicInteger waitToLock=new AtomicInteger(0); public RedisDisLock(String lockKey){
this.lockKey=CommonType.REDISKEY+lockKey;
} public boolean tryLock(Jedis jedis) {
Thread thread = Thread.currentThread();
if(thread==this.getExclusiveOwnerThread()){
return true;
}
Long i = jedis.setnx(lockKey, System.currentTimeMillis()+"");
if(i.intValue()==1){
jedis.expire(lockKey, CommonType.LOCKEXPIRETIME);
setExclusiveOwnerThread(thread);
return true;
}
else{//对于可能性非常低的死锁情况进行解锁
String initTime = jedis.get(lockKey);
if(initTime==null){
LOG.debug("initTime's value is null");
return false;
}
long iniTime=0L;
try{
iniTime = Long.parseLong(initTime);
}
catch(NumberFormatException nfex){
LOG.warn(nfex.getMessage());
jedis.expire(lockKey, 1);
return false;
}
if(((System.currentTimeMillis()-iniTime)/1000-CommonType.LOCKEXPIRETIME-1)>0){
String oldTime = jedis.getSet(lockKey, System.currentTimeMillis()+"");//对于及其极端的情况,lock被线程1处理掉了,但是又被线程2getset新的值了,通过下一次调用trylock()方法处理
if(oldTime==null){
LOG.info("oldTime is null");
return false;
}
if(initTime.equals(oldTime)){
release(jedis);
}
}
}
return false;
} public boolean tryLock(long timeout, TimeUnit unit,Jedis jedis) throws InterruptedException {
long nanosTimeout = unit.toNanos(timeout);
long lastTime = System.nanoTime();
if(tryLock(jedis)){
return true;
}
try{
int waitLockers = waitToLock.getAndIncrement();
if(waitLockers>=CommonType.WAITLOCKERS){
LOG.debug("wait the lock' thread num is much,so return flase");
return false;
}
for(;;){
if(tryLock(jedis)){
return true;
}
if (nanosTimeout <= 0){
LOG.debug("getlock timeout");
return false;
}
if(nanosTimeout>100000){
LockSupport.parkNanos(100000);//中断100毫秒
}
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
if (nanosTimeout <= 0){
LOG.debug("getlock timeout");
return false;
}
if (Thread.interrupted()){
throw new InterruptedException();
}
}
}
finally{
waitToLock.decrementAndGet();
}
} public void unlock(Jedis jedis) {
Thread thread = Thread.currentThread();
if(thread==this.getExclusiveOwnerThread()){
LOG.debug("unlock the thread {}",thread.getId());
release(jedis);
}
} private void release(Jedis jedis){
setExclusiveOwnerThread(null);
jedis.del(lockKey);
} /**
* Sets the thread that currently owns exclusive access. A
* <tt>null</tt> argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* <tt>volatile</tt> field accesses.
*/
protected final void setExclusiveOwnerThread(Thread t) {
exclusiveOwnerThread = t;
} /**
* Returns the thread last set by
* <tt>setExclusiveOwnerThread</tt>, or <tt>null</tt> if never
* set. This method does not otherwise impose any synchronization
* or <tt>volatile</tt> field accesses.
* @return the owner thread
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}

LockSupport类,实际的业务代码首先要通过LockSupport来获取redis锁的对象,再使用

public class LockSupport {

    static ConcurrentHashMap <String,RedisDisLock>lockMap = new ConcurrentHashMap<String,RedisDisLock>();

    public static DisLock getRedisLock(String lockKey){
RedisDisLock lock=null;
if(lockMap.contains(lockKey)){
lock = lockMap.get(lockKey);
}
else{
RedisDisLock lockN = new RedisDisLock(lockKey);
lock = lockMap.putIfAbsent(lockKey, lockN);
if(lock==null){
lock=lockN;
}
}
return lock;
}
}

RedisDisLockTest类是使用此jar的demo代码

public class RedisDisLockTest {

    public static void main(String [] args){
RedisDisLockTest test = new RedisDisLockTest();
//test.testOrder();
//test.testOrder2();
//test.testNOUnlock();
test.testOtherUnlock();
}
public void testOrder(){
JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
OrderThread th = new OrderThread("123456",jedis);
th.start();
}
}
public void testOrder2(){
JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
OrderThread th = new OrderThread("1234567",jedis);
th.start();
}
}
public void testNOUnlock(){
JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
TestNOUnlock th = new TestNOUnlock("12345678",jedis);
th.start();
}
}
public void testOtherUnlock(){
JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){
Jedis jedis = jp.getResource();
TestOtherUnlock th = new TestOtherUnlock("unlock",jedis);
th.start();
}
}
class OrderThread extends Thread{
String lockKey="";
Jedis jedis;
public OrderThread(String lockKey,Jedis jedis){
this.lockKey=lockKey;
this.jedis=jedis;
}
public void run(){
DisLock lock = LockSupport.getRedisLock(lockKey);
try {
if(lock.tryLock(2,TimeUnit.SECONDS,jedis)){
System.out.println("订单"+lockKey+"创建成功!");
lock.unlock(jedis);
}
else{
System.out.println("没有成功获取到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} class TestNOUnlock extends Thread{
String lockKey="";
Jedis jedis;
public TestNOUnlock(String lockKey,Jedis jedis){
this.lockKey=lockKey;
this.jedis=jedis;
}
public void run(){
DisLock lock = LockSupport.getRedisLock(lockKey);
try {
if(lock.tryLock(2,TimeUnit.SECONDS,jedis)){
System.out.println("订单"+lockKey+"创建成功!");
//lock.unlock(jedis);//no unlock
}
else{
System.out.println("没有成功获取到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} class TestOtherUnlock extends Thread{
String lockKey="";
Jedis jedis;
public TestOtherUnlock(String lockKey,Jedis jedis){
this.lockKey=lockKey;
this.jedis=jedis;
}
public void run(){
DisLock lock = LockSupport.getRedisLock(lockKey);
if(lock.tryLock(jedis)){
System.out.println("订单"+lockKey+"创建成功!");
//lock.unlock(jedis);//no unlock
}
else{
lock.unlock(jedis);
System.out.println("TestOtherUnlock没有成功获取到锁");
} }
}
}

  

使用redis来实现分布式锁的更多相关文章

  1. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  2. 一个Redis实现的分布式锁

    import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...

  3. 基于Redis的简单分布式锁的原理

    参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...

  4. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  5. Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...

  6. 在redis上实现分布式锁

    /** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...

  7. 如何用redis正确实现分布式锁?

    先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...

  8. redis系列:分布式锁

    redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  9. 一般实现分布式锁都有哪些方式?使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

    #(1)redis分布式锁 官方叫做RedLock算法,是redis官方支持的分布式锁算法. 这个分布式锁有3个重要的考量点,互斥(只能有一个客户端获取锁),不能死锁,容错(大部分redis节点创建了 ...

  10. Redis如何实现分布式锁

    今天我们来聊一聊分布式锁的那些事. 相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作.在计算机科学中,锁(lock ...

随机推荐

  1. Listview控件实现已选择效果

    Winform中用Listview控件实现更新点击选择后已选择效果,如图: 代码如下: private void frmSelect_Load(object sender, EventArgs e) ...

  2. 10个必看的PHP小代码,很实用!

    获取浏览器IP地址 function getRemoteIPAddress() { $ip = $_SERVER['REMOTE_ADDR']; return $ip; } 如果有代理服务器的情况下获 ...

  3. Sublime Text 2中前端必备的常用插件

    Sublime Text 2安装的插件和所有预置的插件全部在Packages文件下,可以直接通过”preferences“—>”Browse Pakcages“来访问. Sublime Text ...

  4. X-window

    X-Window(也常称为X11或X)系统是一种以位图方式显示的软件视窗系统,最初是1984年麻省理工学院的研究,之后变成UNIX.类UNIX. 以及OpenVMS等操作系统所一致适用的标准化软件工具 ...

  5. OOS升级服务

    给我们的应用程序做个版本更新服务,展示一个安装程序如何实现自动更新. //服务组,添加需要的任何服务 public enum ServerEnum { AutoupdateService,//自动升级 ...

  6. 关于@synchronized(self)的用法

    @synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改.这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其它线程访问,起到线程的保护作 ...

  7. [jobdu]调整数组顺序使奇数位于偶数前面

    这道题的代码没啥好说的,用了O(n)的空间就是水题了.但可以讲一下思考过程.一开始是想O(1)的空间的,然后想从左往右双指针扫,然后根据出现顺序交换遇到的偶数和奇数.但遇到一个问题:1, 2, 3, ...

  8. linux shell中的特殊符号

    该内容,均来自此网址(http://www.92csz.com/study/linux/12.htm).在下只是把那些命令的截图给去了. 你在学习linux的过程中,也许你已经接触过某个特殊符号,例如 ...

  9. 14.8.1 Creating InnoDB Tables 创建InnoDB 表

    14.8.1 Creating InnoDB Tables 创建InnoDB 表 创建一个InnoDB表,使用CREATE TABLE 语句,你不需要指定ENGINE=InnoDB 子句 如果Inno ...

  10. 为什么Nhibernate中属性和方法必须Virtual的

    如果你曾经用过NHibernate 2.0或者更高的版本,那您一定碰到过下面的错误:NHibernate.InvalidProxyTypeException: The following types ...