可重入锁

同一个线程可以多次拿到同一把锁,而不会把自己锁死。这个最开始的时候,其实比较难理解。可以看下面这个不可重入锁的代码来理解。

  1. 模仿一个不可重入锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Lock {
boolean locked = false;

void lock() {
while (locked) {
// 等待锁释放
}
locked = true;
}

void unlock() {
locked = false;
}
}
  1. a()调用b(),他们都有获取锁。但是b()一直获取不到,就会导致死锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
Lock lock = new Lock();

void a() {
lock.lock(); // 第 1 次加锁:成功
b(); // a 里面调用 b
lock.unlock();
}

void b() {
lock.lock(); // 第 2 次加锁:卡死在这里
// ...
lock.unlock();
}

感觉和spring事务那里有点像,后面弄到的时候在对比一下。

如果这个Lock是可重入锁,b()就可以正常的获取锁,不至于导致死锁。以下这些是我们常见的可重入锁:

  • synchronized 也是一个可重入锁
  • ReentrantLockReentrantReadWriteLock 是可重入锁

公平锁和非公平锁

这个其实很好理解:

  • 公平锁:多线程按照申请锁的顺序来获取锁。
  • 非公平锁:多线程不按照申请锁的顺序来获取锁 。

我们常见的公平锁和非公平锁是:

  • synchronized 只支持非公平锁
  • ReentrantLockReentrantReadWriteLock,默认是非公平锁,但支持公平锁

独享锁与共享锁

独享锁与共享锁是一种广义上的说法,从实际用途上来看,也常被称为互斥锁与读写锁。

  • 独享锁:锁一次只能被一个线程所持有。
  • 共享锁:锁可被多个线程所持有。

我们常见的独享锁与共享锁是:

  • synchronizedReentrantLock 只支持独享锁
  • ReentrantReadWriteLock 其写锁是独享锁,其读锁是共享锁。读锁是共享锁使得并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

悲观锁与乐观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是处理并发同步的策略

  • 悲观锁不加锁的并发操作一定会出问题。因此悲观锁适合写操作频繁的场景
  • 乐观锁不加锁的并发操作也没什么问题。对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用不断尝试更新的方式更新数据。因此乐观锁适合读多写少的场景

常见的悲观锁与乐观锁:

  • synchronizedLock
  • CAS以及各种原子类。

偏向锁、轻量级锁、重量级锁

所谓轻量级锁与重量级锁,指的是锁控制粒度的粗细。显然,控制粒度越细,阻塞开销越小,并发性也就越高。

  • Java 1.6 以前,重量级锁一般指的是 synchronized ,而轻量级锁指的是 volatile
  • Java 1.6 以后,针对 synchronized 做了大量优化,引入 4 种锁状态: 无锁状态、偏向锁、轻量级锁和重量级锁。锁可以单向的从偏向锁升级到轻量级锁,再从轻量级锁升级到重量级锁 。
    • 偏向锁 - 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    • 轻量级锁 - 是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    • 重量级锁 - 是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

分段锁

这个有个很经典的就是ConcurrentHashMap,把锁的对象分成多段,每段独立控制,使得锁粒度更细,减少阻塞开销,从而提高并发性。

参考