多线程、锁、CAS和AQS(3)Java中的锁

Java中锁的分类

公平锁/非公平锁

  • 公平锁是指多个线程按照申请锁的顺序来获取锁。如果休眠队列中有线程了,则新进入竞争的线程一定要在休眠队列上排队。

  • 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。新进入的线程是无视休眠队列直接抢占锁的。因此占有锁的线程放弃锁后,唤醒线程需要时间,此时被唤醒的线程就会与新进入的线程争锁。

  • 对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

  • 对于Synchronized synchronized底层实现原理而言,也是一种非公平锁,不能改变。

可重入锁

  • 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

  • Synchronized和Java ReentrantLock都是可重入锁。可重入锁可一定程度避免死锁。如下:

独享锁/共享锁

  • 独享锁是指该锁一次只能被一个线程所持有。

  • 共享锁是指该锁可被多个线程所持有。

  • 对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

  • Synchronized和Java ReentrantLock都是独享锁。

自旋锁

  • 锁的等待者会原地忙等,不停的询问,直到获得锁。采用让当前线程不停地的在循环体内执行实现,当循环的条件被其他线程改变时才能进入临界区。

如何实现可重入锁(ReentrantLock)?

  • 为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应地递减。当计数值为0时,这个锁将被释放。

可重入锁(ReentrantLock)的实现原理

  • 和上面是一样的,细节是ReentrantLock有公平锁模型和非公平锁模型

  • 对于公平锁,如果休眠队列中有线程了,则新进入竞争的线程一定要在休眠队列上排队。

  • 对于非公平锁,新进入的线程是无视休眠队列直接抢占锁的。因此占有锁的线程放弃锁后,唤醒线程需要时间,此时被唤醒的线程就会与新进入的线程争锁。

Java中concurrent包的实现

volatile变量和CAS机制是concurrent包实现的基石

AQS、非阻塞数据结构和原子变量类都是通过volatile和CAS实现的

实现的模式如下:

  • 首先,声明共享变量为volatile;

  • 然后,使用CAS的原子条件更新来实现线程之间的同步;

  • 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS(AbstractQueuedSynchronizer)

AQS(AbstractQueuedSynchronizer),抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch...。

AQS有以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

上面是AQS定义的资源独占方式,其实还有资源共享方式,采用以下两种方法:

  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。