- 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(启动唤醒线程)。
- LockSupport是什么
- 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很是相似。