Java并发编程学习笔记(三)锁

目录

一、分类

(一)内部锁 / 显式锁

(二)公平锁 / 非公平锁

(三)可重入锁 / 不可重入锁

(四)互斥锁 / 读写锁

(五)乐观锁 / 悲观锁

(六)分拆锁 / 分离锁

(七)偏向锁 / 轻量级锁 / 重量级锁

(八)自旋锁

二、内部锁

三、显示锁

四、总结


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中的锁分类

你可能感兴趣的:(Java)