Java中很多锁的名词,并不是全指锁,有的是指锁的特性,有的是指锁的设计,有的是指锁的状态,下面总结的内容是对每个锁的名词进行一定的解释。
乐观锁/悲观锁
乐观锁和悲观锁不是指具体的什么类型的锁,而是指看待并发的角度。
乐观锁其实就是不加锁,乐观认为不加锁的并发操作是没有问题的。它是无锁编程,常常采用CAS算法。并发修改时,进行比较,满足条件就更新,否则再次比较,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
悲观锁认为不加锁肯定会出问题,使用Java提供的各种锁实现加锁。
悲观锁适合写操作比较多的情况,乐观锁则适合读多写少的情况,不加锁会带来大量的性能提升。
可重入锁
可重入锁又名递归锁,当一个线程进入到一个同步方法中,在此方法中要调用另一个同步方法,而且两个方法共用同一把锁,此时线程是可以进入到另一个同步方法中的。
对于 ReentrantLock 而言,从它的名字就可以看出是一个可重入锁,其名字是 Reentrant Lock 重新进入锁。对于 synchronized 而已,也是一个可重入锁,可重入锁的一个好处是能一定程度避免死锁。
public class Demo {
synchronized void setA() throws Exception {
System.out.print("方法A");
setB();
}
synchronized void setB() throws Exception {
System.out.print("方法B");
}
}
上面的代码就是一个可重入锁的一个特点,如果synchronized不是可重入锁,setB方法不会被当前线程执行,从而发生死锁。
读写锁
读写锁的特点:读读共享,读写互斥,写写互斥。加读写锁是防止另外的线程在此时写入数据,防止读取脏数据。ReentrantReadWriteLock 可以实现写锁和读锁,读写可以用一个锁实现。都是读的时候,多个线程可以共享这把锁(可以同时进入),一旦有写的操作,就要一个一个执行。
public class Demo {
private int data; // 共享数据
private ReadWriteLock rwl = new ReentrantReadWriteLock();
// 写数据
public void set(int data) {
rwl.writeLock().lock(); // 取到写锁
try {
System.out.println(Thread.currentThread().getName() + "准备写入数据");
this.data = data;
System.out.println(Thread.currentThread().getName() + "写入" + this.data);
} finally {
rwl.writeLock().unlock(); // 释放写锁
}
}
// 读数据
public void get() {
rwl.readLock().lock(); // 取到读锁
try {
System.out.println(Thread.currentThread().getName() + "准备读取数据");
System.out.println(Thread.currentThread().getName() + "读取" + this.data);
} finally {
rwl.readLock().unlock(); // 释放读锁
}
}
}
分段锁
JDK8之后,去除了真正的分段锁。现在的分段锁并非一种实际的锁,而是一种实现的思想,将数据分段,并在每个分段上单独加锁,锁的粒度进一步细化。例如 ConcurrentHashMap 没有给方法加锁,将 hash 表中的第一个节点当作锁,这样就可以有多把锁,提高了并发操作的效率。
自旋锁
不是实际的锁,是获取锁的方式。例如:原子类,改变内存中的变量,需要不断尝试;synchronized,加锁后,其他线程会不断地尝试获取锁。所谓自旋其实指的就是自己重试,当线程枪锁失败后,重试几次,如果抢到锁了就继续,如果抢不到就阻塞线程,说白了还是为了尽量不阻塞线程。自旋锁是比较消耗CPU的,因为要不断循环重试,不会释放CPU资源。另外,加锁时间普遍较短的场景非常适合自旋锁,可以极大提高锁的效率。
共享锁/独占锁
共享锁
指该锁可被多个线程所持有,并发访问共享资源。比如:读写锁中的读锁,都是读操作时,多个线程可以公用。
独占锁
也叫互斥锁,指该锁一次只能被一个线程所持有。ReentrantLock,synchronized 都是独占锁。但 Lock 的另一个实现类 ReadWriteLock,其读锁是共享锁,其写锁是独占锁。
公平锁/非公平锁
公平锁
按照请求锁的顺序分配,谁先来先获得锁,拥有稳定获得锁的机会。ReentrantLock 默认是非公平锁,但是底层可以通过 AQS 来实现线程调度,所有也可以设置为公平锁
非公平锁
不按照请求锁的顺序分配,谁先抢到就是谁的,不一定拥有获得锁的机会。synchronized 就是一种非公平锁。
// 默认
public ReentrantLock() {
sync = new NonfairSync();
}
// 传入true or false,通过两个内部类实现公平锁或非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}