目录
一、分类
(一)内部锁 / 显式锁
(二)公平锁 / 非公平锁
(三)可重入锁 / 不可重入锁
(四)互斥锁 / 读写锁
(五)乐观锁 / 悲观锁
(六)分拆锁 / 分离锁
(七)偏向锁 / 轻量级锁 / 重量级锁
(八)自旋锁
二、内部锁
三、显示锁
四、总结
Java中的锁大家一定都不陌生,在许多的书籍和文章的并发章节都会提到各种关于锁的名词,下面一起来看看Java中的锁。
内部锁通常指Synchronized锁。
显式锁通常指Lock接口实现的锁,如ReentrantLock。
一种获取锁的策略。
公平锁指按照线程申请锁的顺序获取锁。
非公平锁指不严格按照申请锁的顺序获取锁,如可按优先级获取锁,可能造成饥饿。
Synchronized 是一个典型的非公平锁。
ReentrantLock 可通过构造方法指定是否为公平锁,默认为非公平锁。
可重入锁指线程可以进入它已经获取的锁守护的其他代码块。
不可重入锁指当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会因获取不到而被阻塞。
Synchronized和ReentrantLock都是可重入锁。
互斥锁指一次最多只有一个线程能够占有锁,如Synchronized、ReentrantLock。
读写锁指一个资源可以能够被多个读取线程访问,或被一个写入线程访问,二者不能同时进行,如ReentrantReadWriteLock。
非具体类型的锁,而是理解并发操作的两个角度。
乐观锁认为不加锁的并发操作是可以容忍的,比如原子类,通过CAS实现原子操作。
悲观锁认为不加锁的并发操作一定是不安全的,如使用内部锁或显示锁。
非具体类型的锁,而是一种对于锁的设计。
分拆锁指当一个锁对应多个独立的的独占资源时,可以考虑为每个独占资源分配一个锁。
分离锁指将一个独占资源分为N份,每份分别对应一个独占锁,如ConcurrentHashMap的锁分离实现,使用了一个包含16个锁的Array,每个锁对应HashMap的1/16。
表示Synchronized锁的三种状态,Java5为Synchronized锁引入了锁升级策略,提高了Synchronized锁的性能,这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁升级为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
自旋锁指申请获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
内部锁是由JVM管理的,我们可以通过Synchronized关键字使用内部锁。Synchronized关键字可以用来同步方法或代码块,这也分别代表着不同的同步行为。
同步一个对象
同步一个对象分为两种情况:
其一,类中创建一个对象作为锁对象,如下:
Object lock = new Object();
public void f1() {
synchronized (lock) {
}
}
其二,使用this指代当前对象,如下:
public void f1() {
synchronized (this) {
}
}
在这种情况下,当多个线程访问同一个对象的f1()方法时锁才会生效。
同步一个方法
public synchronized void f2() {
// Do something
}
与同步一个对象作用相同。
同步一个类
public void f3() {
synchronized (Sync.class) {
}
}
作用于整个类,当多个线程调用该类的不同对象的f3() 方法时也会进行同步。
同步一个静态方法
public synchronized static void f4() {
}
静态方法为类所属,因此与同步一个类相同。
java.util.concurrent.locks包中的Lock接口定义了一系列显式锁操作,如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
内部锁虽然使用简单,但锁的获取和释放由JVM实现,在无法获得锁时会无限等待,不能进行中断,而显式锁提供了更加灵活的方法,如可响应中断的锁获取方法,可设定超时的锁获取方法等。
显式锁在使用上更加灵活,能够减小锁的粒度,但也特别需要注意将业务处理放在try代码块中,并在finally代码块中释放锁,如下:
public void f1() {
lock.lock();
try {
// Do something
} finally {
lock.unlock();
}
}
public void f2() {
if (lock.tryLock()) {
try {
// Do something
} finally {
lock.unlock();
}
} else {
}
}
锁是Java并发编程的基础内容,对锁有一定的了解能够构建更好的并发程序,在权衡使用内部锁或显式锁时,要充分考虑使用锁的场景,内部锁由JVM实现,能满足绝大多数使用场景,但如果需要使用可中断、可定时的特性时可以考虑使用显式锁代替内部锁。
Java中的锁分类