redisson 分布式锁

寵の児 提交于 2019-12-04 05:45:35

###需求背景:
在分布式多机部署的情况下,我们要求某一个方法,只能同一个时间只能被一台机器的一个线程执行。

在单机中,有synchronized,读写锁等方式可以解决同步问题。 但是,这些只能作用在同一个机器上,只能保证某一个机器中的方法,不会同时执行。多台机器还是可以同时执行。

这时,就需要借助介质redisson,基于redis的分布式锁。。

前提不多说了,先安装好redis,使用的Redis主从+哨兵模式。这里多台机器安装的redis哨兵,也可以单机多端口安装

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())+"   ";
    }
}

**2.1 当两个IDE同时执行testLock **
IDE-ONE
这里写图片描述
IDE-TWO
这里写图片描述

根据上面时间,可以看到上面2个IDE中的4个线程,都是依次等候执行

2.2 当两个IDE同时执行testTryLock
IDE-ONE
这里写图片描述

IDE-TWO
这里写图片描述

可以看到,在IDE-ONE中,先获取到锁后,休眠了8秒,后面线程,在锁的等待时间5秒内(时间在LockUtil.trylock有设置),无法获取到锁。

2.3 当两个IDE同时测试testSyncro
IDE-ONE

这里写图片描述

IDE-TWO
这里写图片描述

可以看到,在同一个IDE中线程,是排队执行。在不同IDE中,是可以同时执行的。这时就体现了上面分布式锁的作用了

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!