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
32public 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 | public class H2O { |
分布式锁实现
- Mysql
- Redis
-
Zookeeper
使用zookeeper创建临时序列节点来实现分布式锁,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……因为zk节点唯一的,不能重复,节点类型为临时节点, 一台zk服务器创建成功时候,另外的zk服务器创建节点时候就会报错,该节点已经存在。这时候其他的zk服务器就会开始监听并等待。让这台zk服务器的程序现在执行完毕,释放锁。关闭当前会话。临时节点就会消失,并且事件通知Watcher,其他的就会来创建。
无论是临时节点还是持久节点:
① create /abc_00001 那么下次再次 create /abc -> /abc_00002 自动编号;
② create /abc 那么下次再次 create /abc -> 报错;