【Java核心-进阶】synchronized vs ReentrantLock

synchronized

  • synchronized 是 Java 内建的同步机制,提供了互斥的语义和可见性。
  • 当一个线程已经获得某个锁时,试图获取同一个锁的其它线程将只能等待(或阻塞)。
  • 用关键字 synchronized 修饰方法等价于将方法体中的内容用 synchronized 语句块包起来。
public class C {
    private static int s;
    private int a;

    public synchronized void setA(int v) {
        this.a = v;
    }

    public void setA2(int v) {
        synchronized (this) {
          this.a = v;
        }
    }

    public static synchronized void setS(int v) {
        s = v;
    }

    public static void setS(int v) {
        synchronized (C.class) {
            s = v;
        }
    }
}

 

ReentrantLock

  • ReentrantLock 直译为“再入锁”,语义和 synchronized 类似。
    • “再入”是指对 ReentrantLock 的持有是以线程为单位的,而不是基于调用次数。
    • 例:StampedLock 不支持“再入”
      • 当一个线程试图再次获取它已经获得的某个 ReentrantLock 时,获取操作自动成功
        • 这是它内部的同步器类NonfairSync和FairSync实现的,它们都是AbstractQueuedSynchronizer(传说中的AQS)的子类;
        • AQS的父类AbstractOwnableSynchronizer(传说中的AQS)则提供了对互斥的支持。它有一个字段保存了当前拥有它的线程对象的引用。
  • 与 synchronized 相比,ReentrantLock 更用法更灵活,支持更多自定义操作。
    • 可调用 ReentrantLock.lock() 方法获取锁,调用 ReentrantLock.unlock() 方法释放锁。
      • 注:如果编码时未处理好释放锁的操作,可能导致程序一直持有某个ReentrantLock,继而引发异常状况。
        • 如,未规划好异常处理,导致通过 lock() 方法拿到锁后出现异常并跳过了 unlock() 方法
ReentrantLock lock;
...

public void func1() {
    lock.lock();
    doSomethingMayThrowException();
    lock.unlock();
}

public void func2() {
    lock.lock();
    try {
      doSomethingMayThrowException();
    } finally {
      lock.unlock();
    }
}

 

  • 可通过 ReentrantLock 的构造方法指定该锁是公平锁还是非公平锁
    • 公平锁可以将锁给等待时间最久的线程,减少个别线程长期等待锁的现象
    • 只有当确实需要公平时才使用公平锁;因为它会导致吞吐量下降
    • 其构造方法内部会根据参数使用不同的同步器类(Sync)
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

 

 

  • tryLock(long timeout, TimeUnit unit) 方法可设定获取锁的最长等待时间
  • lockInterruptibly() 方法可以在当前线程未处于中断状态时才获取锁
    • 如果当前线程处于中断状态,此方法会抛出InterruptedException
  • 可通过 ReentrantLock.newCondition() 方法创建多个 Condition 对象(java.util.concurrent.locks),并利用这些 Condition 对象组合适应不同的使用场景
    • 通过 Condition 的 await() 和 signal() 等方法就可以方便地实现一些同步需求
    • ArrayBlockingQueue就是通过Condition实现了Blocking语义
final ReentrantLock lock;
private final Condition notEmpty;

// 构造方法
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();

    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);

    // 创建Condition
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

// 获取元素
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            // 如果队列中没有元素,将一直等待
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

// 添加元素
private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;

    count++;

    // 发布队列非空通知。该操作可让take()方法结束等待
    notEmpty.signal();
}

 

 

性能

  • 在锁竞争较低的场景中 synchronized 的性能可能更好
  • 多线程高竞争场景下,ReentrantLock 的性能更好

实际使用时可先侧重设计简单性与代码整洁性,使用 synchronized。

当确定存在性能问题,且经过测试 ReentrantLock 性能更好时再改。

(《解剖一个有缺陷的微基准测试》)

 

性能对比》 

Peter Lawrey 写道
Conclusion
In general, unless you have measured you system and you know you have a performance issue, you should do what you believe is simplest and clearest and it is likely to performance well.

These results indicate that synchronized is best for a small number of threads accessing a lock (<4) and Lock may be best for a high number of threads accessing the same locks.

 

 

其它TODO

线程安全性

  • 线程安全是指,在多线程环境下,共享的、可修改的数据的正确性。
  • 为了达到线程安全,需要保证操作的原子性、可见性、有序性。
    • 原子性:执行某个操作时,其内部子操作的执行不会被其它线程干扰
      • 可以通过同步机制实现。比如,加锁
    • 可见性:某个共享数据被一个线程修改后,其它线程线程能立即知道该数据已被修改
      • Java的 volatile 关键字可以保证其修饰的字段的可见性。《volatile
    • 有序性:对共享数据的读写指令是按照代码中的写明的顺序执行的,不会被编译器优化等其它操作重排

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值