最近看了一些关于锁的文章,下面记录一下自己的理解
0x0 原理
锁是为了解决多线程并发修改或访问同一个资源而设计的。锁的种类有许多种,以我当前所了解的锁,除了乐观锁外,其他锁的原理大同小异:
即在各个线程都能访问的地方设置一个标记来记录某个资源的访问权限。
例如,存在某个资源Target,有A,B,C三个线程去修改Target的值,我们可以设置一个变量
boolean isAccessible = true;
当A线程访问Target时,先要去看isAccessible是否为true,如果为true,则获取访问权,同时将isAccessible设置为false;
此时如果B线程也去访问Target,则发现isAccessible为false,因而获取不到访问权限,那么B线程可以while循环判断isAccessible直到该变量为true时获取访问权;
当一个线程访问完后,将标记isAccessible设为true。
值得注意的是,要保证以上过程正确执行必须满足两个条件:
第一、isAccessible必须保证各个线程都可见,
第二、作为标记的变量的判断和修改需要是原子操作!
首先,可以使用volatile关键字保证其多线程可见性,其次,可以使用java中原子对象例如AtomicBoolean对象及其compareAndSet方法来保证原子操作。
但是,以上例子中,锁的实现是在单进程下的;如果环境是多进程甚至是分布式,如何模拟锁的实现呢?
可以使用第三方缓存来存放各个进程都能访问的标记,而redis及其setnx方法恰恰满足以上作为锁的两大条件,redis可以保证各个线程都能访问,同时setnx (key, value)方法是指:如果这个key存在,返回0;如果key不存在,插入key-value键值对,并返回1。
0x1 例子
先写一个小demo:
redis锁工具类
package com.fly.lock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisLock {
//初始化redis池
private static JedisPoolConfig config;
private static JedisPool pool;
static {
config = new JedisPoolConfig();
config.setMaxTotal(30);
config.setMaxIdle(10);
pool = new JedisPool(config, "192.168.233.200", 6379);
}
/**
* 给target上锁
* @param target
*/
public static void lock(Object target) {
//获取jedis
Jedis jedis = pool.getResource();
//result接收setnx的返回值,初始值为0
Long result= 0L;
while (result < 1) {
//如果target在redis中已经存在,则返回0;否则,在redis中设置target键值对,并返回1
result = jedis.setnx(target.getClass().getName() + target.hashCode(), Thread.currentThread().getName());
}
jedis.close();
}
/**
* 给target解锁
* @param target
*/
public static void unLock(Object target) {
Jedis jedis = pool.getResource();
//删除redis中target对象的键值对
Long del = jedis.del(target.getClass().getName() + target.hashCode());
jedis.close();
}
/**
* 尝试给target上锁,如果锁成功返回true,如果锁失败返回false
* @param target
* @return
*/
public static boolean tryLock(Object target) {
Jedis jedis = pool.getResource();
Long row = jedis.setnx(target.getClass().getName() + target.hashCode(), "true");
jedis.close();
if (row > 0) {
return true;
}
return false;
}
}
测试类
package com.fly.test;
import com.fly.lock.RedisLock;
class Task {
public void doTask() {
//上锁
RedisLock.lock(this);
System.out.println("当前线程: " + Thread.currentThread().getName());
System.out.println("开始执行: " + this.hashCode());
try {
System.out.println("doing...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成: " + this.hashCode());
//解锁
RedisLock.unLock(this);
}
}
public class Demo {
public static void main(String[] args) {
Task task = new Task();
Thread[] threads = new Thread[5];
for (Thread thread : threads) {
thread = new Thread(()->{
task.doTask();
});
thread.start();
}
}
}
输出结果:
----------------------------------------------
当前线程: Thread-0
开始执行: 2081499965
doing...
完成: 2081499965
----------------------------------------------
当前线程: Thread-2
开始执行: 2081499965
doing...
完成: 2081499965
----------------------------------------------
当前线程: Thread-1
开始执行: 2081499965
doing...
完成: 2081499965
----------------------------------------------
当前线程: Thread-4
开始执行: 2081499965
doing...
完成: 2081499965
----------------------------------------------
当前线程: Thread-3
开始执行: 2081499965
doing...
完成: 2081499965
去掉redis锁后,执行结果:
----------------------------------------------
----------------------------------------------
当前线程: Thread-2
开始执行: 1926683415
----------------------------------------------
当前线程: Thread-1
doing...
当前线程: Thread-0
----------------------------------------------
当前线程: Thread-3
开始执行: 1926683415
doing...
开始执行: 1926683415
doing...
----------------------------------------------
开始执行: 1926683415
doing...
当前线程: Thread-4
开始执行: 1926683415
doing...
完成: 1926683415
完成: 1926683415
完成: 1926683415
完成: 1926683415
完成: 1926683415
Process finished with exit code 0
利用redis这个性质,可以实现分布式锁,当然设计一定复杂一些!
redis官方似乎提供了一个redlock,有时间去研究一下。