并发-Lock接口,AQS同步队列,重入锁,读写锁

  • Lock接口
    • 介绍:java中并发包JUC中的lock接口是很多锁的老祖宗,它提供了锁的规范和标准。在java lock接口出现之前,都是依靠synchronized实现多线程的同步访问和资源共享。在java se1.5之后并发包中新增了lock接口以及相关实现类来实现锁的功能,它提供了和synchronized关键字以及相关实现类来实现锁的功能。他和synchronized有相似的同步功能,但是在使用时需要显示的获取和释放锁。synchronized的同步块或者方法都是隐式的获取和释放锁,jvm控制不需要程序员来写代码控制枷锁和释放锁。接口虽然缺少了隐式获取释放锁的便捷性,但是拥有了所得获取与释放的操作性和可中断性,以及超时获取锁等多种synchronized关键字所不具备的同步特性。
    • Lock实现锁与synchronized锁对比
      • 虽然synchronized关键字会隐式获取锁和释放锁,但是他同时将锁的获取和释放固化了,也就是先获取在释放。这种方式简化了同步管理。但是扩展性没有显示的锁获取和释放的好。
      • Synchronized是关键字,内置语言实现,Lock是接口。
      • Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
      • Lock是可以中断锁,Synchronized是非中断锁,必须等待线程执行完成释放锁。
      • Lock可以使用读锁提高多线程读效率。
    • lock接口的特性:
      • 尝试非阻塞的获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则获得锁成功并持有锁。‘
      • 能被中断的获取锁:与synchronized不同,获得锁的线程能响应中断,当获得锁的线程被中断时,抛出中断异常,同时锁被释放;
      • 超时获取锁:在指定截止时间之前获得锁,如果超过时间,仍然无法获得锁,则从当前返会

方法名称

描述

void lock()

当前线程调用该方法去获取锁,获得锁成功之后,从该方法返回。获得锁失败添加到同步队列,线程阻塞。

void lockInterruptibly() throws InterruptedException;

可以中断的获取锁,与lock方法不同之处在于可以响应中断,在获得锁的过程中可以中断当前线程,抛出中断异常。

boolean tryLock();

尝试非阻塞的方式获取锁,调用该方法后立刻返回,获得锁成功则返回true,反之返回false。

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

超时获取锁,当前线程在一下三种情况下会返回:①当前线程在超时时间内获得了锁②当前线程在超时时间内被中断③超时时间结束

void unlock();

释放锁

Condition newCondition();

获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait方法,但是调用该方法,当前线程将会加入到等待队列,同时会释放锁

  • AQS同步队列
    • AQS是一个用来构建锁和同步器的框架,使用AQS能简单有效且高效的构造出应用广泛的大量同步器。
  • 原理概括:
    • 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁的分配机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中

CLH队列是一个虚拟的双向队列(虚拟的双向队列,即不存在队列实例,仅存在节点之间的关联关系)AQS是将每条请求共享资源的线程封装成一个CLH的一个节点来实现锁的分配。

    • 它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
  • 重入锁
    • 重入锁是指任意线程在获取到锁之后能够再次获取该锁而不被阻塞;
      • 线程再次获取锁。锁识别获取锁的线程是不是当前线程,如果是就获取成功;
      • 锁的最终释放。线程n次重复获取锁。然后还要n次释放锁其他线程才能获取到该锁。锁的最终释放要求锁对自己的获取数计数自增,还要会自减。
    • 公平锁和非公平锁的区别:公平性与否是针对获取锁而言的。如果一个锁是公平的,那么锁的获取应该是符合FIFO的:
  • 读写锁
    • 简介:是给一段临界区代码加锁,但是此锁是在写操作时才会互斥,而在进行读的时候是可以共享访问临界区的,
    • 为什么需要读写锁:
      • 在多线程中,有一些公共数据修改的机会比较少,儿读取的机会却非常多,如果每次操作都枷锁,太浪费时间,也浪费资源,降低程序效率。
    • 自旋锁:
      • 自旋锁时发生在获取不到锁的时候会直接等待,不会被cpu直接调度走,而是会一直等到获取锁,因为锁是一直在等待。所以不会有调度开销。所以此锁的效率比挂起要高。但是此锁不会不停的查看锁的释放状态,所以会浪费更多的cpu资源
    • 挂起等待锁
      • 挂起等待是当某线程执行临界区的代码时,那其他线程只能挂起等待这些线程会被cpu调度走,等到锁释放,并且将阻塞的线程调度回来,才可以执行临界区的代码。
  • LockSuppor接口
    • LockSupport是什么
      • LockSupport是一个线程工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,也可以在任意位置唤醒。它的内部其实两类主要的方法:park(停车阻塞线程)和unpark(启动唤醒线程)。
  • Condition接口
    • Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。获取一个Condition必须通过Lock的newCondition()方法。下面通过一个有界队列的示例来深入了解Condition的使用方式。有界队列是一种特殊的队列,当队列为空时,队列的获取操作将会阻塞获取线程,直到队列中有新增元素,当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现“空位”首先需要获得锁,目的是确保数组修改的可见性和排他性。当数组数量等于数组长度时,表示数组已满,则调用notFull.await(),当前线程随之释放锁并进入等待状态。如果数组数量不等于数组长度,表示数组未满,则添加元素到数组中,同时通知等待在notEmpty上的线程,数组中已经有新元素可以获取。在添加和删除方法中使用while循环而非if判断,目的是防止过早或意外的通知,只有条件符合才能够退出循环。回想之前提到的等待/通知的经典范式,二者是非常类似的。
    • 个人理解:这个接口和操作系统中的wait和signal很是相似。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏与ta

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值