0
点赞
收藏
分享

微信扫一扫

juc--三大接口



文章目录

  • juc
  • 一、为什么会有juc
  • 二、juc--三大接口
  • 1.lock
  • 2.condition
  • 3.ReadWriteLock
  • 二、juc--的默认实现类
  • 1.ReentrantLock--lock的默认实现类
  • 公平锁,非公平锁
  • 2. ReentrantReadWriteLock读写锁--ReadWriteLock的默认实现类
  • 1 读写锁和排它锁
  • 2 读写锁原则
  • 总结


juc

juc: java.util.concurrent juc可太牛掰了,几乎完成里面所有的逻辑代码,都是老爷子一个人完成的;

一、为什么会有juc

因为synchronized在很多场景中是无法完美契合的,为了弥补这个缺陷,juc应运而生;说一个最简单的点:synchronized无法知道我是否获取锁成功,无法主动释放锁,当临界区的代码有问题的时候,就会一直阻塞!
synchronized就那么一无是处嘛? no , 例如: synchronized是肯定不会发生死锁的,但是juc中的可重入锁,当时用不当(加锁与解锁没有成对出现)的时候,就会发生死锁!

二、juc–三大接口

juc--三大接口_读锁

1.lock

juc--三大接口_java_02


源码中的示例代码如上:

  • 加锁 lock.lock();
  • 释放锁 lock.unlock(); 一般在finally 中; 目的是可以最终释放锁,不会导致发生意外后,一直阻塞
  • lock 一般不会单独使用

2.condition

juc--三大接口_公平锁_03


Condition源码示例代码如上:

  • 与lock 配合使用
  • 当使用lock 加锁解锁后, 可以用Condition来作为线程之间的通信
  • Condition中的线程通信方法 和Object的wait/notify基本相似。其中,Condition的await方法对应的是Object的wait方法,而Condition的signal/signalAll方法则对应Object的notify/notifyAll()。但Condition类似于Object的等待/通知机制的加强版。
  • juc--三大接口_分布式_04

  • Condition 中有两种队列,分别为 同步队列 和 等待队列
  1. 同步队列为一个双向队列

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

juc--三大接口_读锁_05

每次加入的时候,把线程包装成一个node(包含 获取同步状态失败的线程引用、等待状态以及前驱和后继节点),每次都是根据tail 找到最后一个节点,然后加入,并将tail 指向最后加入的元素; 利用CAS保证安全;

  1. 调用了await之后,当前线程会进入等待队列 ,每次new 的Condition都是一个等到队列,等待队列是一个链式的单项队列,它会把同步队列中的头节点删除并移动到这个等待队列中

nextWaiter;

用Object的方式(wait notify)实际上是指在对象Object对象监视器上只能拥有一个同步队列和一个等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列;
使用 Condition 的时候,多个线程必须获取的是同一把lock ,这样才能调用await 和 singal 等方法;

3.ReadWriteLock

这个可就牛掰了~~,读写锁;

juc--三大接口_读锁_06


可以获取到读锁和写锁; 适用于读多写少的情况;

  • 读锁,允许多个线程同时访问
  • 写锁,可以理解为是synchronized, 临界区的代码同一时间只能有一个线程访问;

二、juc–的默认实现类

1.ReentrantLock–lock的默认实现类

public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

这个锁默认是非公平的锁,且初始化之后无法改变,只能通过 isFair() 获取当前锁的属性;

公平锁,非公平锁

  • NonfairSync和FairSync,分别是非公平锁和公平锁;
  • 这里的“公平”,其实通俗意义来说就是“先来后到”,也就是FIFO。如果对一个锁来说,先对锁获取请求的线程一定会先被满足,后对锁获取请求的线程后被满足,那这个锁就是公平的。反之,那就是不公平的。

一般情况下,非公平锁能提升一定的效率。但是非公平锁可能会发生线程饥饿(有一些线程长时间得不到锁)的情况。所以要根据实际的需求来选择非公平锁和公平锁。
ReentrantLock支持非公平锁和公平锁两种。

2. ReentrantReadWriteLock读写锁–ReadWriteLock的默认实现类

它与ReentrantLock的功能类似,同样是可重入的,支持非公平锁和公平锁。不同的是,它还支持”读写锁“。
有一个弊端: 使用读写锁,在写线程访问时,所有的读线程和其它写线程均被阻塞。

1 读写锁和排它锁

我们前面讲到的synchronized用的锁和ReentrantLock,其实都是“排它锁”。也就是说,这些锁在同一时刻只允许一个线程进行访问。通常说法的锁其实都是排他锁;

而读写锁可以在同一时刻允许多个读线程访问。Java提供了ReentrantReadWriteLock类作为读写锁的默认实现,内部维护了两个锁:一个读锁,一个写锁。通过分离读锁和写锁,使得在“读多写少”的环境下,大大地提高了性能。

2 读写锁原则

  • 读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。
  • 同步状态的高16位用来表示读锁被获取的次数,同步状态的低16位用来表示写锁的获取次数; 高低16位分别维护读写锁的次数;
  • 当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态;

为什么读锁已经被读线程获取,会导致写锁获取失败?
当读锁已经被读线程获取 写锁获取失败的原因是: 写锁必须对读锁保证其可见性,如果获取写锁成功则无法保证其对于读线程的可见性

  • 当写锁被其他线程获取后,读锁获取失败,如果可重入锁,那么写线程如果获取同一把锁,仍会获取成功;
  • 锁降级是写锁降级成为读锁,是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级

总结

juc几乎提供了所有多线程下的,线程通信机制,完美解决了线程中各种场景的问题;


举报

相关推荐

0 条评论