分布式锁的简单实现代码:
需要的jar包: jedis-2.9.0.jar、 commons-pool2-2.4.2.jar
import java.util.List; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import redis.clients.jedis.exceptions.JedisException; public class DistributedLock { private final JedisPool jedisPool; int count = 0; public DistributedLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } public int getCount() { return count; } /** * 加锁 * @param lockName 锁的key * @param acquireTimeout 获取超时时间 * @param timeout 锁的超时时间 * @return 锁标识 */ public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) { Jedis conn = null; String retIdentifier = null; try { // 获取连接 conn = jedisPool.getResource(); // 随机生成一个value String identifier = UUID.randomUUID().toString(); // 锁名,即key值 String lockKey = "lock:" + lockName; // 超时时间,上锁后超过此时间则自动释放锁 int lockExpire = (int) (timeout / 1000); // 获取锁的超时时间,超过这个时间则放弃获取锁 long end = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < end) { if (conn.setnx(lockKey, identifier) == 1) { // 没线程用锁 conn.expire(lockKey, lockExpire); //该线程获取锁,设置锁的过期时间 // 返回value值,用于释放锁时间确认 retIdentifier = identifier; count++; // 这里是统计一个获取了多少次锁 return retIdentifier; } // 返回-1代表key没有设置超时时间,为key设置一个超时时间 if (conn.ttl(lockKey) == -1) { // 已经有线程获取了锁,帮忙检查获取锁的线程有没有帮这个锁设置过期时间,没有设置就帮忙设置 conn.expire(lockKey, lockExpire); } try { Thread.sleep(10); // 线程获取锁之后,模拟线程处理其他业务 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (conn != null) { conn.close(); } } return retIdentifier; } /** * 释放锁 * @param lockName 锁的key * @param identifier 释放锁的标识 * @return */ public boolean releaseLock(String lockName, String identifier) { Jedis conn = null; String lockKey = "lock:" + lockName; boolean retFlag = false; try { conn = jedisPool.getResource(); while (true) { // 监视lock,准备开始事务 Redis Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断 用法: WATCH key [key ...] 总是返回 OK conn.watch(lockKey); // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 if (identifier.equals(conn.get(lockKey))) { // Redis MULTI命令标志着一个事务块的开始。后续将使用EXEC命令执行排队等待原子。 Transaction transaction = conn.multi(); transaction.del(lockKey); // Redis Exec 命令用于执行所有事务块内的命令。 当操作被打断时,返回空值 null 。 List<Object> results = transaction.exec(); if (results == null) { continue; } retFlag = true; } conn.unwatch(); break; } } catch (JedisException e) { e.printStackTrace(); } finally { if (conn != null) { conn.close(); } } return retFlag; } }
测试刚才实现的分布式锁
例子中使用50个线程模拟秒杀一个商品,使用–运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。
模拟秒杀服务,在其中配置了jedis线程池,在初始化的时候传给分布式锁,供其使用。
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class Service { private static JedisPool pool = null; private DistributedLock lock = new DistributedLock(pool); public DistributedLock getLock() { return lock; } public void setLock(DistributedLock lock) { this.lock = lock; }static { JedisPoolConfig config = new JedisPoolConfig(); // 设置最大连接数 config.setMaxTotal(200); // 设置最大空闲数 config.setMaxIdle(8); // 设置最大等待时间 config.setMaxWaitMillis(1000 * 100); // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的 config.setTestOnBorrow(true); pool = new JedisPool(config, "127.0.0.1", 6379, 3000); } public void seckill() { // 返回锁的value值,供释放锁时候进行判断 String identifier = lock.lockWithTimeout("resource", 5000, 1000); System.out.println(Thread.currentThread().getName() + "获得了锁"); lock.releaseLock("resource", identifier); } }
模拟线程进行秒杀服务:
public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { service.seckill(); } public static void main(String[] args) throws InterruptedException { Service service = new Service(); for (int i = 0; i < 50; i++) { ThreadA threadA = new ThreadA(service); threadA.start(); } Thread.sleep(10000);// 等上面的50个线程执行完 DistributedLock a = (DistributedLock) service.getLock(); System.out.println("一共获取了多少次锁 count:" + a.getCount()); } }
console结果: