redis实现分布式锁

自己实现锁时一般要满足以下几个条件:

1、锁要互斥

2、锁要可重入

3、加锁操作要提供阻塞和非阻塞两种模式

4、提供锁释放操作

5、锁要具备失效机制,避免死锁

 

用redis的几个简单命令就能实现分布式锁:
1、setnx
setnx key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0

2、expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁

3、del
del key:删除key

 

直接上代码:

import java.util.concurrent.TimeUnit;

public interface DistributeLock {
    /**
     * 加锁操作,等待直到获取锁成功或者线程被中断
     */
    void lock() throws InterruptedException;

    /**
     * 尝试获取锁,获取失败直接返回
     * @return
     * @throws InterruptedException
     */
    boolean tryLock() throws InterruptedException;

    /**
     * 尝试获取锁,等待到超时或者线程被中断
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 释放锁
     */
    void unlock();
}
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

@Slf4j
public class RedisDistributeLock implements DistributeLock {
    /**
     * 锁名称
     */
    private final String lockName;
    /**
     * 自动解锁时间, 单位秒
     */
    private final int expireTime;
    /**
     * UUID
     */
    private final String uuid;
    /**
     * 线程本地变量, 用于锁重入检查
     */
    private ThreadLocal<AtomicInteger> locks = new ThreadLocal<AtomicInteger>();

    /**
     * Jedis连接池
     */
    private static JedisPool jedisPool;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        // 设置最大连接数
        config.setMaxTotal(200);
        // 设置最大空闲数
        config.setMaxIdle(8);
        // 设置最大等待时间
        config.setMaxWaitMillis(1000 * 100);
        jedisPool = new JedisPool(config, "127.0.0.1", 6379, 3000);
    }

    public RedisDistributeLock(String lockName, int expireTime) {
        this.lockName = lockName;
        this.expireTime = expireTime;
        uuid = UUID.randomUUID().toString();
    }

    public void lock() throws InterruptedException {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 锁重入检查
            if (reentrant(jedis)) {
                return;
            }

            while (true) {
                if (jedis.setnx(lockName, uuid) == 1) {
                    jedis.expire(lockName, expireTime);
                    locks.set(new AtomicInteger(1));
                    return;
                }
                LockSupport.parkNanos(this, 50);

                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            throw new InterruptedException();
        } catch (Exception e) {
            log.error("lock exception, lockName = {}, expireTime = {}, uuid = {}", lockName, expireTime, uuid);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    @Override
    public boolean tryLock() {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 锁重入检查
            if (reentrant(jedis)) {
                return true;
            }

            if (jedis.setnx(lockName, uuid) == 1) {
                jedis.expire(lockName, expireTime);
                locks.set(new AtomicInteger(1));
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("tryLock exception, lockName = {}, expireTime = {}, uuid = {}", lockName, expireTime, uuid);
            return false;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        Jedis jedis = null;

        try {
            jedis = jedisPool.getResource();
            // 锁重入检查
            if (reentrant(jedis)) {
                return true;
            }

            long timeout = System.nanoTime() + unit.toNanos(time);

            while (true) {
                if (jedis.setnx(lockName, uuid) == 1) {
                    jedis.expire(lockName, expireTime);
                    locks.set(new AtomicInteger(1));
                    return true;
                }

                if (System.nanoTime() >= timeout) {
                    return false;
                }

                LockSupport.parkNanos(this, 50);

                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            throw new InterruptedException();
        } catch (Exception e) {
            log.error("tryLock exception, lockName = {}, expireTime = {}, uuid = {}", lockName, expireTime, uuid);
            return false;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    public void unlock() {
        Jedis jedis = null;
        try {
            AtomicInteger atomicInteger = locks.get();
            if (atomicInteger == null) {
                throw new IllegalMonitorStateException("Attempting to unlock without first obtaining that lock on this thread");
            }

            int lockCounts = atomicInteger.decrementAndGet();
            if (lockCounts == 0) {
                locks.remove();

                jedis = jedisPool.getResource();
                if (jedis.get(lockName).equals(uuid)) {
                    jedis.del(lockName);
                }
            }
        } catch (Exception e) {
            log.error("unlock exception");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }

    }

    /**
     * 锁重入检查
     *
     * @return 重入则返回 true; 否则返回 false
     */
    private boolean reentrant(Jedis jedis) {
        try {
            if (locks.get() != null) {
                locks.get().incrementAndGet();
                // reentrant, refresh lease time
                jedis.expire(lockName, expireTime);
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            throw new RuntimeException("Redis refresh lease time failed, key: " + lockName, e);
        }
    }
}

 

posted @ 2018-05-06 16:14  saiQsai  阅读(207)  评论(0编辑  收藏  举报