系统并发锁实现

Posted by 杨一 on 2020-06-11

Jvm 锁

  • synchronized

    • 通过synchronized关键字修饰方法或者代码块,可以达到保证共享变量的线程安全问题。
      synchronized关键字,javac在编译时,会生成对应的monitorenter和monitorexit指令分别对应synchronized同步块的进入和退出,有两个monitorexit指令的原因是为了保证抛异常的情况下也能释放锁,所以javac为同步代码块添加了一个隐式的try-finally, 在finally中会调用monitorexit命令释放锁。

    • 而对于synchronized方法而言,javac为其生成了一个ACC_SYNCHRONIZED关键字,在JVM进行方法调用时, 发现调用的方法被ACC_SYNCHRONIZED修饰,则会先尝试获得锁。

  • ReentrantLock
    ① 等待可中断
    ② 可实现公平锁
    ③ 可实现选择性通知(一个Lock对象可有多个Condition实例,线程对象可注册在其中有选择性的进行通知,更加灵活)

    • 举例解决哲学家进餐问题。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class DiningPhilosophers {
    Semaphore semaphore = new Semaphore(4);
    ReentrantLock[] reentrantLocks = {new ReentrantLock(), new ReentrantLock(), new ReentrantLock(), new ReentrantLock(), new ReentrantLock()};

    public DiningPhilosophers() {

    }

    // call the run() method of any runnable to execute its code
    public void wantsToEat(int philosopher,
    Runnable pickLeftFork,
    Runnable pickRightFork,
    Runnable eat,
    Runnable putLeftFork,
    Runnable putRightFork) throws InterruptedException {

    semaphore.acquire();//进来一个人
    int leftForks = (philosopher + 1) % 5;//这个人的左边叉子编号
    int rightForks = philosopher;//右边的编号
    reentrantLocks[rightForks].lock();//锁叉
    reentrantLocks[leftForks].lock();//锁叉

    pickLeftFork.run();//干活
    pickRightFork.run();//干活
    eat.run();//吃饭
    putLeftFork.run();//放叉子
    putRightFork.run();//放叉子
    reentrantLocks[rightForks].unlock();//解锁
    reentrantLocks[leftForks].unlock();//解锁
    semaphore.release();//退场
    }
    }
  • Semaphore

    • 举例打印H2O
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class H2O {
Semaphore h = new Semaphore(2);
Semaphore o = new Semaphore(0);

public H2O() {

}

public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
h.acquire();
// releaseHydrogen.run() outputs "H". Do not change or remove this line.
releaseHydrogen.run();
o.release();
}

public void oxygen(Runnable releaseOxygen) throws InterruptedException {
o.acquire(2);
// releaseOxygen.run() outputs "O". Do not change or remove this line.
releaseOxygen.run();
h.release(2);
}
}

分布式锁实现

  • Mysql
  • Redis
  • Zookeeper
    使用zookeeper创建临时序列节点来实现分布式锁,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……

    因为zk节点唯一的,不能重复,节点类型为临时节点, 一台zk服务器创建成功时候,另外的zk服务器创建节点时候就会报错,该节点已经存在。这时候其他的zk服务器就会开始监听并等待。让这台zk服务器的程序现在执行完毕,释放锁。关闭当前会话。临时节点就会消失,并且事件通知Watcher,其他的就会来创建。
    无论是临时节点还是持久节点:
    ① create /abc_00001 那么下次再次 create /abc -> /abc_00002 自动编号;
    ② create /abc 那么下次再次 create /abc -> 报错;

    基于 创建节点异常 和 父子顺序首节点监听 实现的Zk分布式锁