synchronized和ReentrantLock有什么区别呢? - 《java核心技术》笔记

ReentrantLock能够实现很多synchronized无法做到的细节控制,比如公平性fairness,或者利用定义条件等。

知识点扩展:

线程安全

线程安全的定义

保证多线程环境下共享的、可修改的状态的正确性。
进而推导出保证线程安全的两个方法:

  • 封装:将对象内部状态隐藏、保护起来。
  • 不可变。

线程安全需要的几个基本特性

  • 原子性:相关操作不会中途被其他线程干扰,一般通过同步机制实现;
  • 可见性:一个线程修改了某个共享变量,其状态能理解被其他线程知晓。
  • 有序性:保证线程内串行语义,避免指令重排。

synchronized和ReentrantLock的使用

synchronized如果用来修饰静态方法,相当于synchronized (ClassName.class) {}

ReentrantLock如果当一个线程试图获取一个它已经获取的锁时,会自动成功。

ReentrantLock可以指定公平性ReentrantLock fairLock = new ReentrantLock(true);,为真时,会倾向于将锁赋予等待时间最久的线程。

ReentrantLock相比synchronized,因为可以像普通对象一样使用,所以可以利用其提供的各种便利方法,进行精细的同步操作:

  • 带超时的获取锁尝试;
  • 判断是否有线程或某个特定线程在排队等待获取锁;
  • 可以响应中断请求;
  • ……

条件变量

如果说 ReentrantLock 是 synchronized的替代,则Condition则是将wait、notify、notifyAll转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。

典型场景

ArrayBlockingQueue

  1. 通过再入锁获取条件变量:

两个条件变量从同一个再入锁创建出来。

/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
private final Condition notFull;
 
public ArrayBlockingQueue(int capacity, boolean fair) {
	if (capacity <= 0)
        throw new IllegalArgumentException();
	this.items = new Object[capacity];
	lock = new ReentrantLock(fair);
	notEmpty = lock.newCondition();
	notFull = lock.newCondition();
}
  1. 队列为空时,等待入队发生。
public E take() throws InterruptedException {
	final ReentrantLock lock = this.lock;
	lock.lockInterruptibly();
	try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
	} finally {
        lock.unlock();
	}
}
  1. 入队后通知notEmpty,触发后续take操作。
private void enqueue(E e) {
	// assert lock.isHeldByCurrentThread();
	// assert lock.getHoldCount() == 1;
	// assert items[putIndex] == null;
	final Object[] items = this.items;
	items[putIndex] = e;
	if (++putIndex == items.length) putIndex = 0;
	count++;
	notEmpty.signal(); // 通知等待的线程,非空条件已经满足
}
comments powered by Disqus