zoukankan      html  css  js  c++  java
  • Redis简单实践-分布式锁

    日常的编码中,我们经常会遇到线程之间操作相同的资源导致并发

      每一种开发技术或许都提供有代码级别的锁来避免这种并发问题,但是当服务器部署多个实例时,代码级别的锁是无法控制这样的并发的,这个时候我们遍可以通过Redis来控制功能对锁的获取,由于应用程序和Redis之间是通过网络来进行交流,无论是单机还是集群,无论是单线程还是多线程,利用Redis来实现锁都能够使用。

      用Redis来创建锁,只要目标则是实现“占坑”,当前上下文我们将其称之为lock,假设一个线程已经占用了这个lock,那么其他的线程则无法再去占用这个lock,假设我们将占用的操作设置为set lock 1,那么则需要让其他的线程无法去set lock 1,常规的流程就是首先,获取到lock,如果lock没有被设置值,则设置进去,占用这个lock,但是同样的,这里在多线程和多实例的情况下会有并发问题,因为【获取到lock,如果lock没有被设置值,则设置进去,占用这个lock】这个操作并不是原子操作,可能在执行过程中,lock就被其他线程给占用设置,这种情况下,会由不止一个线程能够获取到锁。

      为了解决这样的并发问题,在Redis中,提供了setnx指令来将【读取判断设置】的操作原子化,又由于Redis是一个单线程的执行模式,setnx将会只有一个线程能够设置值,通过这样的方法来实现锁的占用。而释放锁的方式则可以通过直接删除这个lcok的key或者设置过期时间,这里不是最主要的讨论目的

      下面列举一个最简单的锁,控制几个线程中,只能由一个线程能够执行到逻辑代码,代码由JAVA编写,访问Redis利用RedisTemplate,在Redistemplate中setnx对应的方法为setIfAbsent代码如下:

      首先建立一个RedisLock的类,编写【读取判断设置】的操作以便于判断是否拿到锁:

    @Component
    public class RedisLock {
    
        @Autowired
        StringRedisTemplate redisTemplate;
    
        public boolean isLock(String lockKey) {
            Boolean result = redisTemplate.opsForValue().setIfAbsent("lock:" + lockKey, "1",
                    Duration.ofSeconds(5));
            if (result) {
                return false;
            } else {
                return true;
            }
        }
    }

      建立一个MyThread来模拟多线程多实例并发占用某个Redis锁:

    public class MyThread extends Thread {
            int i;
            private RedisLock redisLock;
    
            public MyThread(int i, RedisLock redisLock) {
                super();
                this.i = i;
                this.redisLock = redisLock;
            }
    
            @Override
            public void run() {
                final double d = Math.random();
                final int sleep = (int)(d*100);
                try {
              //随机睡眠 Thread.sleep(sleep); }
    catch (InterruptedException e) { e.printStackTrace(); } if (redisLock.isLock("test")) { System.out.println("thread" + i + ": locked"); } else { System.out.println("thread" + i + ": no lock"); } } }

      运行十个线程测试锁的获取情况

      

    @Test
        void contextLoads() throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                Thread thread = new MyThread(i, redisLock);
                thread.start();
            }
    
            Thread.sleep(1000);
        }

    结果截图如下:

      

    上述是一个加锁的简单实现,只有拿到锁的代码可以执行业务逻辑,同时也可以通过对Redis锁的状态监控实现类似代码中的lock等待

      修改RedisLock代码如下:

      

    @Component
    public class RedisLock {
    
        @Autowired
        StringRedisTemplate redisTemplate;
    
        public boolean isLock(String lockKey) {
            Boolean result = redisTemplate.opsForValue().setIfAbsent("lock:" + lockKey, "1",
                    Duration.ofSeconds(5));
            if (result) {
                return false;
            } else {
                return true;
            }
        }
    
        public void unLock(String lockKey) {
            redisTemplate.delete("lock:" + lockKey);
        }
    }

    提供unlock方法以供释放锁

    增加测试类Thread_Wait

     public class MyThread_Wait extends Thread {
            int i;
            private RedisLock redisLock;
    
            public MyThread_Wait(int i, RedisLock redisLock) {
                super();
                this.i = i;
                this.redisLock = redisLock;
            }
    
            @Override
            public void run() {
                try {
                    final double d = Math.random();
                    final int sleep = (int) (d * 100);
                    try {
                        Thread.sleep(sleep);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    int loopCount = 0;
                    String key = "test";
                    while (redisLock.isLock(key)) {
                        loopCount++;
                        Thread.sleep(10);
                    }
    
    
                    Thread.sleep(50);
                    System.out.println("thread" + i + " wait:" + loopCount * 10);
    
                    redisLock.unLock(key);
                } catch (InterruptedException e) {
    
                }
            }
        }

    开启十个线程等待锁的释放:

      

    @Test
        void contextLoads() throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                Thread thread = new MyThread_Wait(i, redisLock);
                thread.start();
            }
    
            Thread.sleep(3000);
        }

    测试结果输出如下:

      

     上述演示了两种获取锁和获取锁的策略的简单实现。

    通过修改代码可以实现,对锁的监听超时时间等等其他功能

    setIfAbsent
  • 相关阅读:
    线程间操作无效: 从不是创建控件“Control Name'”的线程访问它问题的解决方案及原理分析
    C#打印图片
    javascript 地址栏写法
    SQLServer获取Excel中所有Sheet
    C#多页打印实现
    clear在CSS中的妙用
    mitmproxy使用总结
    本地回路抓包问题
    博客园界面优化
    CentOS基于MySQL提供的Yum repository安装MySQL5.6
  • 原文地址:https://www.cnblogs.com/TuringLi/p/12763539.html
Copyright © 2011-2022 走看看