目录
单例模式
校招中考频非常高的一种模式(一个23种设计模式——编程思想,不同场景下如何设计和实现代码的固定套路)
- 所谓的单例模式保证某个类在程序中有且只有一个对象,类比现实生活中的地球类,只有一个地球对象
如何控制只有一个对象呢?
怎么设计这个类的内部对象
- 首先我们知道在外部是创建不了对象的,所以这个对象的类型肯定不能是成员的,必须是静态的,通过类调用
- 属性我们一般都是private修饰
外部怎么访问
- 直接通过getSingleTon方法获取这个唯一的对象
饿汉模式
类加载产生这个唯一的对象,也不管外部是否会调用该对象(饥不择食,这个类一加载就把这个唯一的对象产生了,只要这个类加载到JVM,唯一对象就会产生)
- 饿汉模式是天然的线程安全,因为系统初始化的时候,JVM加载类的时候创建对象
懒汉模式
只有第一次第哦啊有getSingleTon方法,表示外部需要获取这个单例对象才产生对象
- 系统初始化时,外部不需要这个单例对象,只有当外部需要此对象才实例对象
- 不是线程安全的,因为多个线程可能同时调用getLazySingleTon,看到的都是空,创建多个对象
懒汉模式的线程安全解决方式
1最粗暴的直接在静态方法加sychronized
2doube check
- 为什么内层还需要一个判断是否为空,因为外层的sychronized就像一堵墙,可以保证只有一个线程在执行(就像一堵墙一样,但是如果不判断一下,其他线程进入这堵墙之后,也会创建新的对象)
3双重加锁
- 使用volatile关键字保证单例对象初始化不会被中断,保证其他线程获得的对象一定是初始化完成的对象
加锁 / 解锁是一件开销比较高的事情 . 而懒汉模式的线程不安全只是发生在首次创建实例的时候 . 因此后续使用的时候, 不必再进行加锁了 . 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了 .同时为了避免 " 内存可见性 " 导致读取的 instance 出现偏差 , 于是补充上 volatile .
比如现在t1执行到了初始化的实例对象,正在执行new操作,还没完全结束(但是LazySingleTon!=null),然后t2执行到了判断唯一的对象是否为空,对于t2发现不为空,就直接返回了,但是返回的对象是没有完全初始化的,所以需要加volatile,就相当加了一层内存屏障,保证其他线程返回的对象必须等操作完全结束才能执行return语句
阻塞队列
阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
- 或者试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
- 试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来
非堵塞队列
ArrayDeque, (数组双端队列)
ArrayDeque (非堵塞队列)是JDK容器中的一个双端队列实现,内部使用数组进行元素存
储,不允许存储null值,可以高效的进行元素查找和尾部插入取出,是用作队列、双端队
列、栈的绝佳选择,性能比LinkedList还要好。
PriorityQueue, (优先级队列)
PriorityQueue (非堵塞队列) 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象
ConcurrentLinkedQueue, (基于链表的并发队列)
ConcurrentLinkedQueue (非堵塞队列): 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。ConcurrentLinkedQueue的性能要好于BlockingQueue接口,它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。该队列不允
许null元素。
堵塞队列
DelayQueue, (基于时间优先级的队列,延期阻塞队列)
DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。
当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行
排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队
列头部的元素是最早到期的,越往后到期时间赿晚。
ArrayBlockingQueue, (基于数组的并发阻塞队列)
ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据
LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列)
LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。
PriorityBlockingQueue, (带优先级的无界阻塞队列)priorityBlockingQueue是一个无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实comparable接口。
SynchronousQueue (并发同步阻塞队列)
SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
方法名 描述 add() 在不超出队列长度的情况下插入元素,可以立即执行,成功返回true,
如果队列满了就抛出异常。offer() 在不超出队列长度的情况下插入元素的时候则可以立即在队列的尾部插
入指定元素,成功时返回true,如果此队列已满,则返回false。put() 插入元素的时候,如果队列满了就进行等待,直到队列可用。 take() 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有
值,并且该方法取得了该值。poll(long timeout,
TimeUnit unit)在给定的时间里,从队列中获取值,如果没有取到会抛出异常。 remainingCapacity() 获取队列中剩余的空间。 remove(Object o) 从队列中移除指定的值。 contains(Object o) 判断队列中是否拥有该值。 drainTo(Collection
c)将队列中值,全部移除,并发设置到给定的集合中。
阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型.
生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等 待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.比如在 " 秒杀 " 场景下 , 服务器同一时刻可能会收到大量的支付请求 . 如果直接处理这些支付请求 , 服务器可能扛不住( 每个支付请求的处理都需要比较复杂的流程 ). 这个时候就可以把这些请求都放 到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求 .这样做可以有效进行 " 削峰 ", 防止服务器被突然到来的一波请求直接冲垮 .2) 阻塞队列也能使生产者和消费者之间 解耦.比如过年一家人一起包饺子 . 一般都是有明确分工 , 比如一个人负责擀饺子皮 , 其他人负责包 . 擀饺 子皮的人就是 " 生产者 ", 包饺子的人就是 " 消费者 ". 擀饺子皮的人不关心包饺子的人是谁( 能包就行 , 无论是手工包 , 借助工具 , 还是机器包 ), 包饺子的人 也不关心擀饺子皮的人是谁( 有饺子皮就行 , 无论是用擀面杖擀的 , 还是拿罐头瓶擀 , 还是直接从超 市买的).标准库中的阻塞队列在 Java 标准库中内置了阻塞队列 . 如果我们需要在一些程序中使用阻塞队列 , 直接使用标准库中的即可 .BlockingQueue 是一个接口 . 真正实现的类是 LinkedBlockingQueue和ArrayBlockingQueue等
- put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
- BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
- 必须先生产 ,再消费,不然会阻塞
package thread.blockedQueue; import java.util.Random; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class Test { public static void main(String[] args) throws InterruptedException { BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(3); Thread customer = new Thread(() -> { while (true) { try { // 当阻塞队列为空,take方法就会阻塞 int val = blockingQueue.take(); System.out.println("消费元素 : " + val); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "消费者"); Random random = new Random(); Thread producer = new Thread(() -> { while (true) { try { int val = random.nextInt(100); // 当队列已满,put方法就会阻塞 blockingQueue.put(val); System.out.println("生产元素 : " + val); Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "生产者"); customer.start(); producer.start(); } }
阻塞队列的大小
- 通过构造方法来确定阻塞队列的大小
- 若没有声明数字,就表示无界
阻塞队列的实现
- 通过 "循环队列" 的方式来实现.
- 使用 synchronized 进行加锁控制.
- put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一 定队列就不满了, 因为同时可能是唤醒了多个线程).
- take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
生产者消费者模型的作用是什么
这个问题很理论,但是很重要:
- 1 通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
- 2解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约
定时器
定时器也是软件开发中的一个重要组件 . 类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定 好的代码.(比如我们说小爱同学,3分钟后关机),web阶段检测客户端的连接,500ms没收到数据,断开连接JAVA实现的定时器
- 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
- schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).第三个参数可以用来设置循环时间间隔执行
定时器的实现