需求背景:
在分布式多机部署的情况下,我们要求某一个方法,只能同一个时间只能被一台机器的一个线程执行。
在单机中,有synchronized,读写锁等方式可以解决同步问题。 但是,这些只能作用在同一个机器上,只能保证某一个机器中的方法,不会同时执行。多台机器还是可以同时执行。
这时,就需要借助介质redisson,基于redis的分布式锁。。
前提不多说了,先安装好redis,使用的Redis主从+哨兵模式。这里多台机器安装的redis哨兵,也可以单机多端口安装
添加依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>
1.1首先创建RedissonManager 获取redisson
package com.test.redisson.manager;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* @version 1.0.0
* @description
* @date 2018/05/04 16:54
**/
public class RedissonManager {
private static RedissonClient redissonClient;
private static Config config = new Config();
/**
* 初始化Redisson,使用哨兵模式
*/
public static void init(){
try {
config.useSentinelServers()
.setMasterName("cache")
.addSentinelAddress("10.0.0.1:26379","10.0.0.2:26379", "10.0.0.3:26379")
//同任何节点建立连接时的等待超时。时间单位是毫秒。默认:10000
.setConnectTimeout(30000)
//当与某个节点的连接断开时,等待与其重新建立连接的时间间隔。时间单位是毫秒。默认:3000
.setReconnectionTimeout(10000)
//等待节点回复命令的时间。该时间从命令发送成功时开始计时。默认:3000
.setTimeout(10000)
//如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。默认值:3
.setRetryAttempts(5)
//在一条命令发送失败以后,等待重试发送的时间间隔。时间单位是毫秒。 默认值:1500
.setRetryInterval(3000)
;
redissonClient = Redisson.create(config);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 获取Redisson的实例对象
* @return
*/
public static Redisson getRedisson(){
init();
return (Redisson) redissonClient;
}
/**
* 测试Redisson是否正常
*/
public static void main(String[] args) {
Redisson redisson = RedissonManager.getRedisson();
System.out.println("redisson = " + redisson);
}
}
1.2 锁操作LockUtil
package com.test.redisson.lockutil;
import com.test.redisson.manager.RedissonManager;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
/**
* Redisson分布式锁 工具类
*/
public class LockUtil {
private static Redisson redisson = RedissonManager.getRedisson();
/**
* 根据name对进行上锁操作,redisson Lock 一直等待获取锁
* @param lockname
*/
public static void lock(String lockname) throws InterruptedException {
String key = lockname;
RLock lock = redisson.getLock(key);
//lock提供带timeout参数,timeout结束强制解锁,防止死锁
lock.lock(60L, TimeUnit.SECONDS);
}
/**
* 根据name对进行上锁操作,redisson tryLock 根据第一个参数,一定时间内为获取到锁,则不再等待直接返回boolean。交给上层处理
* @param lockname
*/
public static boolean tryLock(String lockname) throws InterruptedException {
String key = lockname;
RLock lock = redisson.getLock(key);
//tryLock,第一个参数是等待时间,5秒内获取不到锁,则直接返回。 第二个参数 60是60秒后强制释放
return lock.tryLock(5L,60L, TimeUnit.SECONDS);
}
/**
* 根据name对进行解锁操作
* @param lockname
*/
public static void unlock(String lockname){
String key = lockname;
RLock lock = redisson.getLock(key);
lock.unlock();
}
}
1.3 RedissonTestLock(测试封装方法)
package com.test.redisson.test;
import com.test.redisson.lockutil.LockUtil;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @version 1.0.0
* @description
* @date 2018/05/04 17:02
**/
public class RedissonTestLock {
public static void main(String[] args) {
//模拟2个线程
for (int i = 1; i <= 2; i++) {
//可以开2个IDE,分别测试以下三个方法
//打开2个IDE同时执行时,这里可以分别取不同名,区分
new Thread("IDE-ONE-"+i) {
@Override
public void run() {
/**
* 测试testLock结果,每个IDE中线程,依次排队等待获取锁。然后执行任务
*/
testLock("redissonlocktest_testkey");
/**
* 测试testTryLock结果,每个IDE中线程,在TryLock的等待时间范围内,若获取到锁,返回true,则执行任务;若获取不到,则返回false,直接返回return;
*/
// testTryLock("redissonlocktest_testkey");
/**
* 测试testSyncro结果,IDE之间的线程互不影响,同一个IDE中的线程排队值执行,不同IDE之间的互补影响,可同时执行
*/
// testSyncro("redissonlocktest_testkey");
}
}.start();
}
}
//测试lock,拿不到lock就不罢休,不然线程就一直block。
public static void testLock(String preKey) {
try {
System.out.println(getDate()+Thread.currentThread().getName() + "准备开始任务。。。。");
LockUtil.lock(preKey);
System.out.println(getDate()+Thread.currentThread().getName() + "模拟正在执行任务。。。。");
Thread.sleep(5000);//等待5秒,后面的所有线程都“依次”等待5秒,等待获取锁,执行任务
} catch (Exception e) {
System.out.println(getDate()+"线程锁 :" + Thread.currentThread().getId() + " exception :" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
e.printStackTrace();
} finally {
LockUtil.unlock(preKey);
System.out.println(getDate()+Thread.currentThread().getName() + "释放。。。。");
}
}
//带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。
public static void testTryLock(String preKey) {
try {
System.out.println(getDate()+Thread.currentThread().getName() + "准备开始任务。。。。");
boolean falg = LockUtil.tryLock(preKey);
//这里若获取不到锁,就直接返回了
if(!falg){
System.out.println(getDate()+Thread.currentThread().getName() + "--没有获取到锁直接返回--" + falg);
return;
}
System.out.println(getDate()+Thread.currentThread().getName() + "--获取锁--" + falg);
System.out.println(getDate()+Thread.currentThread().getName() + "模拟正在执行任务。。。。");
//由于在LockUtil.tryLock设置的等待时间是5s,所以这里如果休眠的小于5秒,这第二个线程能获取到锁,
// 如果设置的大于5秒,则剩下的线程都不能获取锁。可以分别试试2s,和8s的情况
Thread.sleep(8000);//等待6秒
} catch (Exception e) {
System.out.println(getDate()+"线程锁 :" + Thread.currentThread().getId() + " exception :" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
e.printStackTrace();
} finally {
try {
LockUtil.unlock(preKey);
System.out.println(getDate()+Thread.currentThread().getName() + "释放。。。。");
}catch (Exception e){
e.printStackTrace();
}
}
}
//synchronized 这种锁只能锁住同一台机器的线程,若部署多台机器,则不能锁住
public static void testSyncro(String preKey) {
synchronized (preKey.intern()){//为什么要intern前篇文章有解释
try {
System.out.println(getDate()+Thread.currentThread().getName() + "准备开始任务。。。。");
System.out.println(getDate()+Thread.currentThread().getName() + "--获取锁--" );
System.out.println(getDate()+Thread.currentThread().getName() + "模拟正在执行任务。。。。");
Thread.sleep(6000);//执行2秒
} catch (Exception e) {
System.out.println(getDate()+"线程锁 :" + Thread.currentThread().getId() + " exception :" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
e.printStackTrace();
} finally {
try {
System.out.println(getDate()+Thread.currentThread().getName() + "释放。。。。");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public static String getDate(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+" ";
}
}
redisson分布式锁的常用示例
模拟多线程竞争同一个资源,利用redisson分布式锁实现
public void testMultiThread() throws Exception{
final Config config = new Config();
config.useSingleServer().setAddress("192.168.3.199:6379").setConnectionPoolSize(5);
//开启多线程去竞争同一个资源锁
for (int i = 0; i < 12; i++) {
new Thread(new Runnable() {
@SuppressWarnings("static-access")
@Override
public void run() {
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock"); //设置锁对象
boolean res = false;
try {
// 尝试加锁,最多等待10秒,上锁以后3秒自动解锁
res = lock.tryLock(10, 3, TimeUnit.SECONDS);
if(res){//获取锁成功
try {
//业务操作
Thread.currentThread().sleep(new Random().nextInt(2)*1000);
}finally{
lock.unlock();
}
}
System.err.println("ThreadID:"+Thread.currentThread().getId()+",res="+res); }
catch (InterruptedException e) {
log("尝试获取锁异常:"+e);
}
}}).start();
}
}