目录
校招中考频非常高的一种模式(一个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语句
阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
- 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
- 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型.
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等 待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.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)
定时器也是软件开发中的一个重要组件 . 类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定 好的代码.(比如我们说小爱同学,3分钟后关机),web阶段检测客户端的连接,500ms没收到数据,断开连接JAVA实现的定时器
- 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
- schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).第三个参数可以用来设置循环时间间隔执行
定时器的实现