分布式锁
并发编程中的锁并发编程的锁机制:synchronized和lock。在单进程的系统中,当存在多个线程可以同时改变某个变量时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。
而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。
分布式环境下,数据一致性问题一直是一个比较重要的话题,而又不同于单进程的情况。分布式与单机情况下最大的不同在于其不是多线程而是多进程。多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上, 因此需要将标记存储在一个所有进程都能看到的地方。 常见的是秒杀场景,订单服务部署了多个实例,如
在上面的场景中,商品的库存是共享变量,面对高并发情形,需要保证对资源的访问互斥。在单机环境中,java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单纯的java API 并不能提供分布式锁的能力。分布式系统中,由于分布式系统的分布性,即多线程和多进程并且分布在不同机器中,synchronized和lock这两种锁将失去原有锁的效果,需要我们自已实现分布式锁。
常见的分布式锁如下:
使用setnx实现分布式锁
setnx key value
setnx是将key的值设为value,当且仅当key不存在。若给定的key已经存在,则setnx不做任何动作。
返回1,说明该进程获得锁,setnx将键(lock.id)的值设置为锁的超时时间,当前时间+加上锁的有效时间。
返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断地尝试setnx操作,以获得锁。
存在死锁的问题
在线程释放锁,即执行del lock.id操作前,需要先判断锁是否已超时。如果锁已超时,那么锁可能已由其他线程获得,这时直接执行del lock.id操作会导致把其他线程已获得的锁释放掉。
获取分布式锁
public boolean lock( long timeout, TimeUnit timeUnit ) throws InterruptedException
{
timeout = timeUnit.toMillis( timeout );
long time = timeout + System.currentTimeMillis();
lock.tryLock( timeout, timeUnit );
try{
while ( true )
{
boolean hasLock = tryLock();
if ( hasLock )
{
return(true); /* 获得锁 */
}else if ( timeout < System.currentTimeMillis() )
{
break;
}
Thread.sleep( 1000 );
}
} finally {
if ( lock.isHeldByCurrentThread() )
{
lock.unlock();
}
}
return(false);
}
public boolean tryLock()
{
long time = System.currentTimeMillis();
long timeout = 2000;
String expires = String.valueOf( timeout + time );
if ( redisService.setnx( "lock.id", expires ) > 0 )
{
/* 获取锁,设置超时时间 */
setLockStatus( expires );
return(true);
}else{
String locktime = redisService.get( "lock.id" );
/* 检查锁是否超时 */
if ( locktime != null && Long.parseLong( locktime ) < time )
{
String oldlocktime = redis.getset( "lock.id", expires );
/* 旧值与当前时间比较 */
if ( oldlocktime != null && locktime.equals( oldlocktime ) )
{
/* 获取锁,设置超时时间 */
setLockStatus( expires );
return(true);
}
}
return(false);
}
}
释放锁
public boolean unlock()
{
if ( lockHolder == Thread.currentThread() )
{
/* 判断锁是否超时,没有超时才将互斥量删除 */
if ( lockExpiresTime > System.currentTimeMillis() )
{
redisService.del( "lock.id" );
}
lockHolder = null;
return(true);
}else{
throws new IllegalMonitorStateException( "无法执行解锁操作" );
}
}
如何一起学习,有没有免费资料?
欢迎工作一到五年的 Java 的工程师朋友们加入的 Java 架构开发:770590461
本群提供免费的学习指导架构资料以及免费的解答
不懂得问题都可以在本群提出来之后还会有职业生涯规划以及面试指导