首先介绍以下锁,以下面减少库存案例讲解
普通情况
代码:
public class Stock {
//库存数量
private static int num=1;
public boolean reduceStock(){
if(num>0){
num--;
return true;
}else{
return false;
}
}
}
public class StockMain implements Runnable {
public void run() {
boolean b=new Stock().reduceStock();
if(b){
System.out.println(Thread.currentThread().getName()+"减少库存成功!");
}else{
System.out.println(Thread.currentThread().getName()+"减少库存失败!");
}
}
public static void main(String []args){
new Thread(new StockMain(),"线程1").start();
new Thread(new StockMain(),"线程2").start();
}
}
结果:谁先抢到谁成功,也有可能前一个抢到没执行完毕,后一个线程也进入判断
,,
线程安全问题
public class Stock {
//库存数量
private static int num=1;
public boolean reduceStock() throws InterruptedException {
if(num>0){
Thread.sleep(1000);
num--;
return true;
}else{
return false;
}
}
}
public class StockMain implements Runnable {
public void run() {
boolean b= false;
try {
b = new Stock().reduceStock();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(b){
System.out.println(Thread.currentThread().getName()+"减少库存成功!");
}else{
System.out.println(Thread.currentThread().getName()+"减少库存失败!");
}
}
public static void main(String []args){
new Thread(new StockMain(),"线程1").start();
new Thread(new StockMain(),"线程2").start();
}
}
结果:先抢到线程的进入方法后,沉睡一秒,足够后面的线程进入判断条件,所以全部成功,产生线程安全问题
加入锁机制
public class Stock {
//库存数量
private static int num=1;
public boolean reduceStock() throws InterruptedException {
if(num>0){
Thread.sleep(1000);
num--;
return true;
}else{
return false;
}
}
}
public class StockMain implements Runnable {
private static Lock lock=new ReentrantLock();
public void run() {
boolean b= false;
try {
//上锁
lock.lock();;
b = new Stock().reduceStock();
//解锁
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(b){
System.out.println(Thread.currentThread().getName()+"减少库存成功!");
}else{
System.out.println(Thread.currentThread().getName()+"减少库存失败!");
}
}
public static void main(String []args){
new Thread(new StockMain(),"线程1").start();
new Thread(new StockMain(),"线程2").start();
}
}
结果:只有前一个线程执行完毕后,后一个才会执行
引出问题:分布式系统集群环境下,负载均衡之后,服务不可能只发给一台机器,锁机制已经不满足现状,分布式锁出现。
分布式锁
1.数据库实现分布式锁的思路分析(操作同一数据库)
数据库分布式锁实现
创建表
CREATE TABLE `lock_record` (
`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键',
`lock_name` VARCHAR ( 50 ) DEFAULT NULL COMMENT '锁名称',
PRIMARY KEY ( `id` ),
UNIQUE KEY `lock_name` ( `lock_name` )
) ENGINE = INNODB AUTO_INCREMENT = 38 DEFAULT CHARSET = utf8
定义锁
实现Lock接口,tryLock()尝试获取锁,从锁表中查询指定的锁记 录,如果查询到记录,说明 已经上锁,不能再上锁
上锁
在lock方法获取锁之前先调用tryLock()方法尝试获取锁,如果未加锁则向锁表中插入一条锁记录来获取 锁,这里我们通过循环,如果上锁我们一致等待锁的释放
释放锁
即是将数据库中对应的锁表记录删除
import com.itheima.demo.bean.LockRecord;
import com.itheima.demo.mapper.LockRecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Component
public class DbLock implements Lock {
private static final String LOCK_NAME = "db_lock_stock";
@Autowired
private LockRecordMapper lockRecordMapper;
//上锁
@Override
public void lock() {
while(true){
if(tryLock()){
//向锁表中插入一条记录
LockRecord lockRecord = new LockRecord();
lockRecord.setLockName(LOCK_NAME);
lockRecordMapper.insertSelective(lockRecord);
return;
}else{
System.out.println("等待锁.......");
}
}
}
//尝试获取锁
@Override
public boolean tryLock() {
//查询lockRecord的记录
Example example = new Example(LockRecord.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("lockName",LOCK_NAME);
LockRecord lockRecord = lockRecordMapper.selectOneByExample(example);
if(lockRecord==null){
return true;
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
//释放锁的操作
@Override
public void unlock() {
Example example = new Example(LockRecord.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("lockName",LOCK_NAME);
lockRecordMapper.deleteByExample(example);
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
}
2.redis实现分布式锁
redis分布式锁的实现基于setnx(set if not exists),设置成功,返回1;设置失败,返回0,
释放锁的操作通过del指令来完成 如果设置锁后在执行中间过程时,程序抛出异常,导致del指令没有调用,锁永远无法释放,这样就会 陷入死锁。所以我们拿到锁之后会给锁加上一个过期时间,这样即使中间出现异常,过期时间到后会自动释放锁。同时在setnx 和 expire 如果进程挂掉,expire不能执行也会死锁。所以要保证setnx和expire是一个原子性操作即可。
redis 2.8之后推出了setnx和expire的组合指令
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Component
public class RedisLock implements Lock {
private static final String LOCK_NAME = "redis_stock_lock";
@Autowired
private RedisTemplate redisTemplate;
@Override
public void lock() {
while(true){
//上锁 setnx
// Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lockName", LOCK_NAME);
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lockName",LOCK_NAME,10,TimeUnit.SECONDS);
//思考 锁过期怎么办?如何保证锁不过期 锁的自动续期 20 18 20 20
if(isLock){
return;
}else{
System.out.println("等待锁........");
}
}
}
@Override
public void unlock() {
// 删除指定的锁的key
redisTemplate.delete("lockName");
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
分布式锁的redisson实现:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
获取锁,释放锁
Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDataba se(0);
Redisson redisson = (Redisson) Redisson.create(config);
RLock mylock = redisson.getLock(key);
//获取锁
mylock.lock();
//释放锁
mylock.unlock();
3.zookeeper实现分布式锁
zookeeper通过创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建 临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch 来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推
释放锁
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class ZkLock implements Lock {
//zk客户端
private ZooKeeper zk;
//zk是一个目录结构,locks
private String root = "/locks";
//锁的名称
private String lockName;
//当前线程创建的序列node
private ThreadLocal<String> nodeId = new ThreadLocal<>();
//用来同步等待zkclient链接到了服务端
private CountDownLatch connectedSignal = new CountDownLatch(1);
private final static int sessionTimeout = 3000;
private final static byte[] data= new byte[0];
public ZkLock(String config, String lockName) {
this.lockName = lockName;
try {
zk = new ZooKeeper(config, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 建立连接
if (event.getState() == Event.KeeperState.SyncConnected) {
connectedSignal.countDown();
}
}
});
connectedSignal.await();
Stat stat = zk.exists(root, false);
if (null == stat) {
// 创建根节点
zk.create(root, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
class LockWatcher implements Watcher {
private CountDownLatch latch = null;
public LockWatcher(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted)
latch.countDown();
}
}
@Override
public void lock() {
try {
// 创建临时子节点
String myNode = zk.create(root + "/" + lockName , data, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName()+myNode+ "created");
// 取出所有子节点
List<String> subNodes = zk.getChildren(root, false);
TreeSet<String> sortedNodes = new TreeSet<>();
for(String node :subNodes) {
sortedNodes.add(root +"/" +node);
}
String smallNode = sortedNodes.first();
if (myNode.equals( smallNode)) {
// 如果是最小的节点,则表示取得锁
System.out.println(Thread.currentThread().getName()+ myNode+"get lock");
this.nodeId.set(myNode);
return;
}
String preNode = sortedNodes.lower(myNode);
CountDownLatch latch = new CountDownLatch(1);
Stat stat = zk.exists(preNode, new LockWatcher(latch));// 同时注册监听。
// 判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
if (stat != null) {
System.out.println(Thread.currentThread().getName()+myNode+
" waiting for " + root + "/" + preNode + " released lock");
latch.await();// 等待,这里应该一直等待其他线程释放锁
nodeId.set(myNode);
latch = null;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void unlock() {
try {
System.out.println(Thread.currentThread().getName()+ "unlock ");
if (null != nodeId) {
zk.delete(nodeId.get(), -1);
}
nodeId.remove();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}

- 在队列节点下创建临时顺序节点 例如/queue_info/192.168.1.1-0000001
- 调用getChildren()接口来获取/queue_info节点下所有子节点,获取队列中所有元素
- 比较自己节点是否是序号最小的节点,如果不是,则等待其他节点出队列,在序号最小的节点注册 watcher
- 获取watcher通知后,重复步骤