并发编程技术(四)

上一节讲了sychronized用法及实现原理,详细请回顾《 》,今天我们继续讲解ReentrantLock。

ReentrantLock的介绍

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重入性(关于synchronized上节已经讲过),synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。

lock 与sychronized的区别

  1. sychronized是一个关键字,Lock是一个对象
  2. sychronized的锁释放是被动的,只有执行完或出现异常时才会释放。lock可以判断锁的判断
  3. lock可以指定是公平锁或非公平锁,sychronized是非公平锁,不能指定

重入读写锁 ReentrantReadWriteLock的作用是什么

当我们在执行读操作的时候,它首先获取一个读锁。在并发访问时,读它不会被阻塞,因为读不会改变数据本身的状态。写操作时线程必须要获取一个写锁,当其它线程持有写锁的情况下,当前线程在写操作时会获取不到这个锁,那么这个锁会被阻塞,只有这个锁被释放以后其它的写才可以继续。同样,当前线程获得到写锁后,其它线程在读操作时会被阻塞,因为在写锁时有可能数据会发生变化。在写锁释放后读操作才可以进入读取数据。

其实就是通过读写锁来提升并发操作的性能,使用读写锁能够在读多写少的场景下,大大提升我们操作性能。

读-读是共享

读-写是不共享

写-读是不共享

写-写是不共享

谈到ReentrantLock,不得不谈的AbstractQueuedSynchronizer。

ReentrantLock实现图


并发编程技术(四)


ReentrantLock非公平锁和公平锁的源码

//非公平锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//公平锁
final void lock() {
acquire(1);
}

ReentrantLock提供两个构造方法

//默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}


什么是公平和非公平锁

公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

Condition线程间通信

他提供的是在JDK层面上实现对锁的控制。在多线程中用来协调通信的工具,跟wait/notify是完全一样的。也就是可以让某些线程在某个条件的情况下,只有满足某个条件的时候线程才会被唤醒,若不满足,则不会唤醒。

Condition的实现原理和基本使用方法

1、Condition提供了await()方法将当前线程阻塞,并提供signal()方法支持另外一个线程将已经阻塞的线程唤醒。

2、Condition需要结合Lock使用

3、线程调用await()方法前必须获取锁,调用await()方法时,将线程构造成节点加入等待队列,同时释放锁,并挂起当前线程

4、其他线程调用signal()方法前也必须获取锁,当执行signal()方法时将等待队列的节点移入到同步队列,当线程退出临界区释放锁的时候,唤醒同步队列的首个节点

为什么在调用condition.await()和condition.signal()时要获取锁?

是因为需要通过这个线程等待释放后仍然能够竞争锁,Condition队列唤醒后重新加入AQS

AQS的介绍

提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础。使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此子类对于状态的把握,需要使用这个同步器提供的以下三个方法对状态进行操作:

  • getState()
  • setState(int)
  • compareAndSetState(int, int)


AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)

AQS中的Node是核心,它是一个链表,AQS 有两个锁 共享锁和独占锁

static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;

链表的头节点和尾节点,源码

private transient volatile Node head;
private transient volatile Node tail;

对应CAS的方法

compareAndSetHead()

compareAndSetTail()

CompareAndSet 的实现是都是由 Unsafe.compareAndSwapObject 实现,其中Unsafe是jvm中的后门。

compareAndSetState方法中

  • state=0 表示无锁
  • state>0表示有锁
private volatile long state;


并发编程技术(四)


唤醒线程的方式

  • Thread.currentThread().interrupted()
  • LockSupport.unpark()

------------------


分享到:


相關文章: