西维蜀黍

【Java】锁 - ReentrantLock 类

ReentrantLock 类

ReentrantLock 重入锁,是实现了 Lock 接口的一个类,也是在实际编程中使用频率很高的一个锁。

ReentrantLock 具有以下特点:

  • 支持重入性(reentrancy),表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。
  • 支持**公平锁(fair locking)非公平锁(unfair locking)**两种方式。
  • 是**可中断(Interruptible)**锁,即是一个可以被中断持有的锁。
  • 是**可限时锁(Timed Lock)**锁,可以传入一个超时值作为参数。当在到达超时时间前,仍然没有获得锁,则放弃对锁的持有申请。
  ...


【Java】多线程 - Java 锁的演化

锁存在的问题

Java 在 JDK1.5 之前都是靠 synchronized 关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁(exclusive locking)其实就是一种悲观锁(pessimistic locking),所以说 synchronized 是一种悲观锁。

  ...


【Java】多线程-Java 保证原子性、有序性、可见性

线程安全(Thread Safety)

概念

当一个可变(mutable)对象会被多个线程访问时,就要考虑这个线程是不是需要被设计成线程安全的。

一个类会被称为**线程安全(thread-safe)**的,当它被从多个线程访问,而且无论这些线程如何被调度(scheduling)和交叉(interleaving)执行,而且在调用代码(calling code)中不需要额外的同步(synchronization)或者其他协调(coordiantion)机制,它的行为仍然正确(若预期执行)。

换句话说,线程安全的类封装(encapsulate)了已经需要的同步机制,因此客户端或者说调用者不再需要关注或者提供这些同步机制。

  ...


【Java】多线程 - 原子类(Atomic Classes)

背景

我们知道 volatile 关键字虽然轻量级,但不能保证原子性,synchronized 可以保证原子性,但是比较重量级。

那么有没有一种简单的、性能高的方法来保证 Java 的原子操作呢?答案当然是有的。

在 JDK1.5 时期, Java 家族加入了 Atomic 包。

原子类(Atomic Classes)

Java 中提供了对应的原子操作类来实现原子地且有条件进行的读-改-写操作操作。其本质是利用了 CPU 级别的原子指令。由于原子类是基于 CPU 级别的指令,因此其开销比需要操作系统参与的锁的开销小。

CAS 算法

CAS (比较与交换,Compare and swap) 算法是一种非阻塞算法(non-blocking algorhithm),同时也是一种无锁算法(lock-free algorhithm),基于乐观并发(pessimistic concurrent control)的思想。

非阻塞版本相对于基于锁的版本有几个性能优势。首先,它用硬件的原生形态代替 JVM 的锁定代码路径,从而在更细的粒度层次上(独立的内存位置)进行同步,失败的线程也可以立即重试,而不会被挂起后重新调度。更细的粒度降低了争用的机会,不用重新调度就能重试的能力也降低了争用的成本。即使有少量失败的 CAS 操作,这种方法仍然会比由于锁争用造成的重新调度快得多。

以 Atomic 类中的 incrementAndGet() 方法为例,其内部就调用了Unsafe中的 native 方法(CompareAndSet)以实现递增数值:

private volatile int value;
public final int get() {
        return value;
    }
/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

我们来分析下incrementAndGet的逻辑:

  1. 先获取当前的value值
  2. 对value加一
  3. 第三步是关键步骤,调用compareAndSet方法来进行一个原子更新操作,这个方法的语义是:先检查当前value是否等于current,如果相等,则意味着value没被其他线程修改过,更新并返回true。如果不相等,compareAndSet则会返回false,然后循环继续尝试更新。
  ...


【Java】锁 - CAS 无锁算法

CAS 是怎么实现的

以 Atomic 类中的 incrementAndGet() 方法为例,其内部就调用了Unsafe中的 native 方法(CompareAndSet)以实现递增数值:

private volatile int value;
public final int get() {
        return value;
    }
/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

我们来分析下 incrementAndGet 的逻辑:

  1. 先获取当前的value值
  2. 对value加一
  3. 第三步是关键步骤,调用compareAndSet方法来进行一个原子更新操作,这个方法的语义是:先检查当前value是否等于current,如果相等,则意味着value没被其他线程修改过,更新并返回true。如果不相等,compareAndSet则会返回false,然后循环继续尝试更新。

  ...