java利用redis实现分布式锁

假设有这样的场景,我们用多线程给redis里面一个变量,比如money=1000做减法,每次将变量-100,这样,10次操作之后,我们期望的是0,在实际中,我们通过编码来看看这个结果:

RedisUtil.java 用jedis线程池来操作redis

package com.xxx.redis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisUtil {
	public static final JedisPool pool;
	static{
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxIdle(200);
		config.setMaxWaitMillis(1000 * 100);
		config.setTestOnBorrow(false);
		pool = new JedisPool(config, "127.0.0.1", 6379);
	}
}

DistributedLock.java

package com.xxx.redis;
import java.util.concurrent.TimeUnit;
import redis.clients.jedis.Jedis;
public class DistributedLock {
	
	public static Runnable notLock() throws InterruptedException{
		return ()->{
			try (Jedis jedis = RedisUtil.pool.getResource()) {
				calImpl(jedis);
			} catch (Exception e) {
				e.printStackTrace();
			}
		};
	}

	public static void calImpl(Jedis jedis) {
		int money = Integer.parseInt(jedis.get("money"));
		jedis.set("money", String.valueOf(money-100));
		System.out.println("calc -> " + Thread.currentThread().getName() );
	}
	
	public static void main(String[] args) throws Exception{
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			jedis.set("money", "1000");
		} catch (Exception e) {
			e.printStackTrace();
		}
		Runnable runnable = notLock();
		for(int i=0;i<10;i++){
			Thread thread  = new Thread(runnable,String.valueOf(i));
			thread.start();
		}
		TimeUnit.SECONDS.sleep(5);
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			int money = Integer.parseInt(jedis.get("money"));
			System.out.println("money -> "+money);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

普通的代码,不使用锁来操作,结果不是我们想象中的那样:

java利用redis实现分布式锁_第1张图片 

这个结果,出乎了我们的意料,多线程环境下,最终的money并不是0,而是400,而且,每次运行结果可能都不一样。这就是多线程环境下,造成的变量问题。 解释的话,就是当一个线程获取到了redis中money的值比如900,还没有进行赋值-100操作的时候,另一个线程也获取了redis中money的值900,这样,他也来操作,在同一时间里,两个线程同时做了-100操作,但是结果却是一样的,最终经过10次这样的-100操作,我们的money就可能变为了我们运行打印的结果了。

既然问题出在了获取money的值并做减法操作这个过程中,那么,问题就可以有解决办法了,分布式锁,这就是本文要介绍的:

 分布式锁的实现方式有很多,这里使用redis来实现分布式锁:

自定义Lock.java接口:

package com.xxx.redis;
import redis.clients.jedis.Jedis;
public interface Lock {
	public boolean tryLock(Jedis jedis,String key,String val,Long ttl);
	public boolean releaseLock(Jedis jedis,String key,String val);
}

实现Lock.java接口 LockInitial.java:

package com.xxx.redis;
import java.util.Random;
import redis.clients.jedis.Jedis;
public class LockInitial implements Lock{
	public static Random random = new Random();
	
	@Override
	public boolean tryLock(Jedis jedis,String key,String val,Long ttl){
		String result = jedis.set(key, val,"NX","EX",ttl);
		return "OK".equals(result);
	}
	
	@Override
	public boolean releaseLock(Jedis jedis,String key,String val){
		if(val.equals(jedis.get(key))){
			return jedis.del(key) > 0;
		}
		return false;
	}
}

完整的DistributedLock.java:

package com.xxx.redis;

import java.util.concurrent.TimeUnit;

import redis.clients.jedis.Jedis;

public class DistributedLock {
	
	public static Runnable notLock() throws InterruptedException{
		return ()->{
			try (Jedis jedis = RedisUtil.pool.getResource()) {
				calImpl(jedis);
			} catch (Exception e) {
				e.printStackTrace();
			}
		};
	}
	
	public static Runnable lock() throws InterruptedException{
		LockInitial lockInitial = new LockInitial();
		return ()->{
			try (Jedis jedis = RedisUtil.pool.getResource()) {
				calc(lockInitial,jedis);
			} catch (Exception e) {
				e.printStackTrace();
			}
		};
	}
	
	
	public static void calc(LockInitial lockInitial,Jedis jedis){
		if(lockInitial.tryLock(jedis, "money_lock", "100", 1000l)){
			calImpl(jedis);
			lockInitial.releaseLock(jedis, "money_lock", "100");
		}else{
			calc(lockInitial, jedis);
		}
	}
	
	public static void calImpl(Jedis jedis) {
		int money = Integer.parseInt(jedis.get("money"));
		jedis.set("money", String.valueOf(money-100));
		System.out.println("calc -> " + Thread.currentThread().getName() );
	}
	
	public static void main(String[] args) throws Exception{
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			jedis.set("money", "1000");
		} catch (Exception e) {
			e.printStackTrace();
		}
		Runnable runnable = lock();
		for(int i=0;i<10;i++){
			Thread thread  = new Thread(runnable,String.valueOf(i));
			thread.start();
		}
		TimeUnit.SECONDS.sleep(5);
		try (Jedis jedis = RedisUtil.pool.getResource()) {
			int money = Integer.parseInt(jedis.get("money"));
			System.out.println("money -> "+money);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

使用分布式锁之后,运行打印结果:

java利用redis实现分布式锁_第2张图片 

最终结果符合我们的预期,1000经过10次-100操作,最终变为0。

分布式锁实现的核心在于:需要获取一把锁,这个锁只有一把,一个线程获取之后,其他线程进入等待,某一线程获取锁,并且运行完了相关操作,表示锁用完了,这时候需要调用释放锁的方法,让别的线程获得锁,继续运行下一步的操作。 

你可能感兴趣的:(java)