一、锁使用场景
对公共区域数据并发访问,为防止数据脏读,脏写而使用锁。下面举例如下:从redis中取数据,做一定处理,再将处理后的数据压入,当多线程或多进程对同一个key对应的数据做处理时,就可能由于并发导最后的结果不是预期想要的。
二、锁的使用流程
锁的大体使用流程都是获取锁,做数据处理,然后释放锁,流程图如下:
三、多线程同步锁
如果只是涉及单进程的多线程加锁,最简单的方法是在方法前加synchronized即可,当然这种方法有个缺点:如果同一个对象中所方法加了该锁,那某个线程调用该对象的一个方法,整个对象都会被锁住。
解决办法:1、封装不通对象。 2、在方法里去加锁
四、分布式锁
现在很多系统为了并发性、稳定性、扩展性,都是分布式部署,同一个服务会部署多套,而synchronized只能对单服务/进程起作用,多服务就锁不住了。这时需要使用到分布式锁,分布式锁其实原理也是一样的,多服务共用一套mysql、redis,这时通过mysql、redis去锁就能启动分布式锁的效果。下面以redis为例。
- 当只是对redis的key值做简单的自增某个值的操作,那这时只需要用redis的特定方法,redis天生就支持并发锁机制。
直接调用redis的这个方法,这一个命令就包括读key对应的值,然后增加delta Long increment(K key, long delta);
2、有些场景操作比较复杂,如根据key读出list列表,然后根据一定逻辑对list做处理,再将处理后的list压入,这时用redis自带的可能就不行了,得自己根据redis已有的方法封装分布式锁(当然,这种情况其实能避免就得尽量避免的,因为自己去封装的稳定速度都可能会差些)。
加锁代码如下,当然这种方法还还是可能有问题,因为加锁和设置锁超时没有保持原子性,新版本redis可以直接将两个放到一条命令中,保持原子性
public boolean lock(String value)
{
if (jedisTemplate.setnx(LOCK_KEY, value))
{
jedisTemplate.expire(LOCK_KEY,5,TimeUnit.SECONDS);
return true;
}
return false;
}
解锁代码如下,增加value校验,是为了防止A进程的锁超时了,却还去释放锁,结果可能把B进程的锁给释放了。使用脚本,是为了保证原子性。
public boolean unlock(String value)
{
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del',KEYS[1]) " +
"else" +
" return 0 " +
"end";
try
{
Object result = jedis.eval(script, Collections.singletonList(LOCK_KEY),
Collections.singletonList(value));
if("1".equals(result.toString()))
{
return true;
}
return false;
}
catch(Exception e)
{
return false;
}
finally
{
jedis.close();
}
}