Java中的可重入锁和不可重入锁
锁的简单应用
用lock来保证原子性(this.count++这段代码称为临界区)
什么是原子性,就是不可分,从头执行到尾,不能被其他线程同时执行。
可通过CAS来实现原子操作
CAS(Compare and Swap):
CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
CAS主要通过compareAndSwapXXX()方法来实现,而这个方法的实现需要涉及底层的unsafe类
unsafe类: java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作
这里有个介绍原子操作的博客
https://my.oschina.net/xinxingegeya/blog/499223
还有对unsafe类详解的博客
http://www.cnblogs.com/mickole/articles/3757278.html
1 public class Counter{
2 private Lock lock = new Lock();
3 private int count = 0;
4 public int inc(){
5 lock.lock();
6 this.count++;
7 lock.unlock();
8 return count;
9 }
10 }
不可重入锁
设计如下:
1 public class Lock{
2 private boolean isLocked = false;
3 public synchronized void lock() throws InterruptedException{
4 while(isLocked){
5 wait();
6 }
7 isLocked = true;
8 }
9 public synchronized void unlock(){
10 isLocked = false;
11 notify();
12 }
13 }
这其实是个不可重入锁,举个例子
1 public class Count{
2 Lock lock = new Lock();
3 public void print(){
4 lock.lock();
5 doAdd();
6 lock.unlock();
7 }
8 public void doAdd(){
9 lock.lock();
10 //do something
11 lock.unlock();
12 }
13 }
当调用print()方法时,获得了锁,这时就无法再调用doAdd()方法,这时必须先释放锁才能调用,所以称这种锁为不可重入锁,也叫自旋锁。
可重入锁
设计如下:
1 public class Lock{
2 boolean isLocked = false;
3 Thread lockedBy = null;
4 int lockedCount = 0;
5 public synchronized void lock()
6 throws InterruptedException{
7 Thread thread = Thread.currentThread();
8 while(isLocked && lockedBy != thread){
9 wait();
10 }
11 isLocked = true;
12 lockedCount++;
13 lockedBy = thread;
14 }
15 public synchronized void unlock(){
16 if(Thread.currentThread() == this.lockedBy){
17 lockedCount--;
18 if(lockedCount == 0){
19 isLocked = false;
20 notify();
21 }
22 }
23 }
24 }
相对来说,可重入就意味着: 一个线程可以进入任何一个 该线程 已经拥有的锁所同步着的代码块
第一个线程执行print()方法,得到了锁,使lockedBy等于当前线程,也就是说,执行的这个方法的线程获得了这个锁,执行add()方法时,同样要先获得锁(即调用lock.lock()),因不满足while循环的条件(isLocked(=true,因为该线程调用print()方法时就获得该锁了);但是这里与不可重入锁的区别是有个lockedBy(即表示现在哪个线程持有锁);因为调用add()方法的是和调用print()的是同一个线程),也就是不等待,继续进行,将此时的lockedCount变量,也就是当前获得锁的数量加一,当释放了所有的锁(即得调用获得锁数量次数的unlock),才执行notify()。
如果在执行这个方法时,有第二个线程想要执行这个方法,因为lockedBy不等于第二个线程,导致这个线程进入了循环,也就是等待(阻塞),不断执行wait()方法。只有当第一个线程释放了所有的锁(即第一个线程调用了多少次lock()方法就得调用多少次unlock()方法释放锁),执行了notify()方法,第二个线程才得以跳出循环,继续执行。
这就是可重入锁的特点。
可重入锁与不可重入锁对比, 简单来说就是:可重入锁会多两个属性(1、获得该锁的线程;2、获得该锁的次数),根据第一个属性判断,如果是持有该锁的那个线程又来lock,不会被阻塞(wait),而是在上锁的次数加一(表示这个线程又锁了一次(重入)),只有该线程unlock的次数达到上锁的次数(即第二个属性等于0),才会唤醒其他线程。
java中常用的可重入锁
synchronized
java.util.concurrent.locks.ReentrantLock