锁的基本概念到 Redis 分布式锁实现
Posted SegmentFault
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了锁的基本概念到 Redis 分布式锁实现相关的知识,希望对你有一定的参考价值。
package Thread;
import java.util.concurrent.TimeUnit;
public class Ticket {
/**
* 初始库存量
* */
Integer ticketNum = 8;
public void reduce(int num){
//判断库存是否够用
if((ticketNum - num) >= 0){
try {
TimeUnit.MILLISECONDS.sleep( 200);
} catch (InterruptedException e){
e.printStackTrace();
}
ticketNum -= num;
System.out.println(Thread.currentThread().getName() + "成功卖出"
+ num + "张,剩余" + ticketNum + "张票");
} else {
System.err.println(Thread.currentThread().getName() + "没有卖出"
+ num + "张,剩余" + ticketNum + "张票");
}
}
public static void main(String[] args) throws InterruptedException{
Ticket ticket = new Ticket();
//开启10个线程进行抢票,按理说应该有两个人抢不到票
for( int i= 0;i< 10;i++){
new Thread(() -> ticket.reduce( 1), "用户" + (i + 1)).start();
}
Thread.sleep( 1000L);
}
}
public synchronized void reduce( int num){
//判断库存是否够用
if((ticketNum - num) >= 0){
try {
TimeUnit.MILLISECONDS.sleep( 200);
} catch (InterruptedException e){
e.printStackTrace();
}
ticketNum -= num;
System.out.println(Thread.currentThread().getName() + "成功卖出"
+ num + "张,剩余" + ticketNum + "张票");
} else {
System.err.println(Thread.currentThread().getName() + "没有卖出"
+ num + "张,剩余" + ticketNum + "张票");
}
}
2.1 缩短锁的持有时间
public void reduceByLock( int num){
boolean flag = false;
synchronized (ticketNum){
if((ticketNum - num) >= 0){
ticketNum -= num;
flag = true;
}
}
if(flag){
System.out.println(Thread.currentThread().getName() + "成功卖出"
+ num + "张,剩余" + ticketNum + "张票");
}
else {
System.err.println(Thread.currentThread().getName() + "没有卖出"
+ num + "张,剩余" + ticketNum + "张票");
}
if(ticketNum == 0){
System.out.println( "耗时" + (System.currentTimeMillis() - startTime) + "毫秒");
}
}
public synchronized void reduce( int num){
//判断库存是否够用
if((ticketNum - num) >= 0){
try {
TimeUnit.MILLISECONDS.sleep( 200);
} catch (InterruptedException e){
e.printStackTrace();
}
ticketNum -= num;
if(ticketNum == 0){
System.out.println( "耗时" + (System.currentTimeMillis() - startTime) + "毫秒");
}
System.out.println(Thread.currentThread().getName() + "成功卖出"
+ num + "张,剩余" + ticketNum + "张票");
} else {
System.err.println(Thread.currentThread().getName() + "没有卖出"
+ num + "张,剩余" + ticketNum + "张票");
}
}
2.2 减少锁的粒度
package Thread;
import java.util.concurrent.CountDownLatch;
public class Movie {
private final CountDownLatch latch = new CountDownLatch( 1);
//魔童哪吒
private Integer babyTickets = 20;
//蜘蛛侠
private Integer spiderTickets = 100;
public synchronized void showBabyTickets() throws InterruptedException{
System.out.println( "魔童哪吒的剩余票数为:" + babyTickets);
//购买
latch.await();
}
public synchronized void showSpiderTickets() throws InterruptedException{
System.out.println( "蜘蛛侠的剩余票数为:" + spiderTickets);
//购买
}
public static void main(String[] args) {
Movie movie = new Movie();
new Thread(() -> {
try {
movie.showBabyTickets();
} catch (InterruptedException e){
e.printStackTrace();
}
}, "用户A").start();
new Thread(() -> {
try {
movie.showSpiderTickets();
} catch (InterruptedException e){
e.printStackTrace();
}
}, "用户B").start();
}
}
魔童哪吒的剩余票数为:20
public void showBabyTickets() throws InterruptedException{
synchronized (babyTickets) {
System.out.println( "魔童哪吒的剩余票数为:" + babyTickets);
//购买
latch.await();
}
}
public void showSpiderTickets() throws InterruptedException{
synchronized (spiderTickets) {
System.out.println( "蜘蛛侠的剩余票数为:" + spiderTickets);
//购买
}
}
魔童哪吒的剩余票数为:20
蜘蛛侠的剩余票数为:100
2.3 锁分离
-
公平锁:ReentrantLock -
非公平锁:Synchronized、ReentrantLock、cas -
悲观锁:Synchronized -
乐观锁:cas -
独享锁:Synchronized、ReentrantLock -
共享锁:Semaphore
4.1 分布式锁需要注意哪些点
1)互斥性
2)防死锁
3)持锁人解锁
4)可重入
4.2 Redis 分布式锁流程
-
设置锁的有效时间 目的是防死锁 (非常关键)
-
分配客户端的唯一标识,目的是保证持锁人解锁 (非常重要)
4.3 加锁和解锁
4.3.1 加锁
public String set( String key, String value, String nxxx, String expx, long time) {
this.checkIsInMultiOrPipeline();
this.client. set(key, value, nxxx, expx, time);
return this.client.getStatusCodeReply();
}
4.3.2 解锁
-
检查是否自己持有锁 (判断唯一标识) ; -
删除锁。
if Redis. call( "get", key==argv[ 1]) then
return Redis. call( "del", key)
else return 0 end
public class RedisDistributedLock implements Lock {
//上下文,保存当前锁的持有人id
private ThreadLocal<String> lockContext = new ThreadLocal<String>();
//默认锁的超时时间
private long time = 100;
//可重入性
private Thread ownerThread;
public RedisDistributedLock() {
}
public void lock() {
while (!tryLock()){
try {
Thread.sleep( 100);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
public boolean tryLock() {
return tryLock(time,TimeUnit.MILLISECONDS);
}
public boolean tryLock(long time, TimeUnit unit){
String id = UUID.randomUUID().toString(); //每一个锁的持有人都分配一个唯一的id
Thread t = Thread.currentThread();
Jedis jedis = new Jedis( "127.0.0.1", 6379);
//只有锁不存在的时候加锁并设置锁的有效时间
if( "OK". equals(jedis. set( "lock",id, "NX", "PX", unit.toMillis(time)))){
//持有锁的人的id
lockContext. set(id); ①
//记录当前的线程
setOwnerThread(t); ②
return true;
} else if(ownerThread == t){
//因为锁是可重入的,所以需要判断当前线程已经持有锁的情况
return true;
} else {
return false;
}
}
private void setOwnerThread(Thread t){
this.ownerThread = t;
}
public void unlock() {
String script = null;
try{
Jedis jedis = new Jedis( "127.0.0.1", 6379);
script = inputStream2String(getClass().getResourceAsStream( "/Redis.Lua"));
if(lockContext. get()== null){
//没有人持有锁
return;
}
//删除锁 ③
jedis.eval(script, Arrays.asList( "lock"), Arrays.asList(lockContext. get()));
lockContext. remove();
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 将InputStream转化成String
* @param is
* @return
* @throws IOException
*/
public String inputStream2String(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = -1;
while ((i = is.read()) != -1) {
baos.write(i);
}
return baos.toString();
}
public void lockInterruptibly() throws InterruptedException {
}
public Condition newCondition() {
return null;
}
}
-
用一个上下文全局变量来记录持有锁的人的 uuid,解锁的时候需要将该 uuid 作为参数传入 Lua 脚本中,来判断是否可以解锁。 -
要记录当前线程,来实现分布式锁的重入性,如果是当前线程持有锁的话,也属于加锁成功。 -
用 eval 函数来执行 Lua 脚本,保证解锁时的原子性。
-
数据库如果挂掉会导致业务系统不可用。 -
无法设置过期时间,会造成死锁。
6.2 基于 zookeeper 的分布式锁
-
从性能角度:Redis > zookeeper > 数据库 -
从可靠性 (安全) 性角度:zookeeper > Redis > 数据库
-
互斥性 -
防死锁 -
加锁人解锁 -
可重入性
以上是关于锁的基本概念到 Redis 分布式锁实现的主要内容,如果未能解决你的问题,请参考以下文章