针对需求场景通过Java现有的语法,达成了某个类只能被创建出一个实例这样的效果,创建了多个实例就会编译报错。
1 饿汉模式
class Singleton {
// 先把实例创建出来
private static Singleton instance = new Singleton();
//使用这个唯一实例,统一通过Singleton.getInstance()方法来获取
public static Singleton getInstance(){
return instance;
}
//为了避免Singleton类不小心被复制多份出来
//把构造方法设为private,在类外面就无法通过new的方式来创造Singleton实例了
private Singleton(){}
}
public class ThreadDemo12 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1==s2); // true
//Singleton s3 = new Singleton();//编译报错,无法创建出多个实例
}
}
Singleton和实例无关和类相关。
static是单例模式的灵魂,保证这个实例唯一,保证这个实例在类加载的时候就被创建出来,让当前instance属性是类属性。
类属性是类对象的属性,类对象又是唯一实例(类加载阶段被创建出的一个实例)。
类加载阶段:运行一个Java程序,就需要让Java进程能够找到并读取对应.class文件内容,并解析,构造成类对象……这一系列操作的过程。
2 懒汉模式
2.1 懒汉模式 (线程不安全版)
class SingletonLazy{
//把构造方法设为private,在类外面就无法通过new的方式来创造Singleton实例了
private SingletonLazy(){}
private static SingletonLazy instance = null; // 创建了一个实例的引用并指向null
// 这个实例并非是类加载的时候创建,
// 而是真正第一次使用的时候,才去创建的(如果不用就不创建)
// 使用这个唯一实例,统一通过Singleton.getInstance()方法来获取
public static SingletonLazy getInstance(){
if(instance == null) {
// 这里有读,比较和写操作而且不是原子的,是线程不安全的
// 线程安全问题发生在首次创建实例时.
// 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例
instance = new SingletonLazy();
}
return instance;
}
}
public class ThreadDemo13 {
}
2.2 懒汉模式 (线程安全1.0版)
// 版本一:
class SingletonLazy{
private SingletonLazy(){}
private static SingletonLazy instance = null;
public static SingletonLazy getInstance(){
synchronized (SingletonLazy.class) {
//这里的加锁是在new出对象之前加上是有必要的,
// 加锁会使开销变大
if(instance == null) {
//一旦对象new完了后续调用getInstance时,此时instance的值一定是非空的,
//因此就会直接触发return,相当于一个是比较操作 一个是 返回操作,
//这两个操作都是读操作不加锁也没有线程安全问题
instance = new SingletonLazy();
}
}
return instance;
}
}
// 版本二:
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class ThreadDemo13 {
}
2.3 懒汉模式 (线程安全2.0版)
class SingletonLazy{
private static SingletonLazy instance = null;
public static SingletonLazy getInstance(){
if(instance == null) {
// 当没有instance对象的时候才加锁,减少不必要的开销
synchronized (SingletonLazy.class) {
if(instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){}
}
public class ThreadDemo13 {
}
2.4 懒汉模式 (线程安全3.0版)
class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
使用双重 if 判定, 降低锁竞争的频率,给 instance 加上了 volatile
加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候. 因此后续使用的时候, 不必再进行加锁了。
外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了,同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是补充上 volatile。
当多线程首次调用 getInstance可能都发现 instance 为 null, 于是继续往下执行来竞争锁, 其中竞争成功的线程, 再完成创建实例的操作,当实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住. 也就不会继续创建其他实例。
具体执行流程:
假设有多个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同一把锁。其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.当线程1 释放锁之后, 线程2 或 线程3 也拿到锁, 也通过里层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从而不再尝试获取锁了. 降低了开销。
3 阻塞式队列
阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,可以有效进行 “削峰”。
阻塞队列也能使生产者和消费者之间 解耦.
3.1 标准库中的阻塞队列
在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可 BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue put 方法用于阻塞式的入队列, take 用于阻塞式的出队列. BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性。
案例:
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();
3.2 阻塞队列实现
通过 “循环队列” 的方式来实现.
使用 synchronized 进行加锁控制.
put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).
take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
案例:
public class BlockingQueue {
private int[] items = new int[1000];
private volatile int size = 0;
private int head = 0;
private int tail = 0;
public void put(int value) throws InterruptedException {
synchronized (this) {
// 此处最好使用 while.
// 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
// 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
// 就只能继续等待
while (size == items.length) {
wait();
}
items[tail] = value;
tail = (tail + 1) % items.length;
size++;
notifyAll();
}
}
public int take() throws InterruptedException {
int ret = 0;
synchronized (this) {
while (size == 0) {
wait();
}
ret = items[head];
head = (head + 1) % items.length;
size--;
notifyAll();
}
return ret;
}
public synchronized int size() {
return size;
}
// 测试代码
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new BlockingQueue();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
blockingQueue.put(random.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
producer.start();
customer.join();
producer.join();
}
}
3.3 生产者消费者模型
"生产者消费者模型"是阻塞队列的一个典型应用场景。
生产者消费者模型就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.
案例一:
public class Demo01{
public static void main(String[] args) {
Product p = new Product(); //产品
//创建生产对象
Producer producer = new Producer(p);
//创建消费者
Customer customer = new Customer(p);
//调用start方法开启线程
producer.start();
customer.start();
}
}
//产品类
class Product {
String name; //名字
double price;//价格
boolean flag = false; //产品是否生产完毕的标识,默认情况是没有生产完成
}
//生产者
class Producer extends Thread{
Product p; //产品
public Producer(Product p){
this.p = p;
}
@Override
public void run(){
while (true){
synchronized (p){
if (p.flag==false){
p.name = "苹果";
p.price = 6.5;
System.out.println("生产者生产出了:"+p.name+"价格是:"+p.price);
p.flag=true;
p.notify();//换新消费者去消费
}else {
//已经生产完毕,等待消费者先去消费
try{
p.wait(); // 让生产者等待
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
}
//消费者
class Customer extends Thread{
Product p;
public Customer(Product p){
this.p = p;
}
@Override
public void run(){
while (true){
synchronized (p){
if (p.flag==true){//产品已经生产完毕
System.out.println("消费者消费了:"+p.name+"价格:"+p.price);
p.flag = false; // 产品已经消费完毕
p.notify();//唤醒生产者去生产
}else{
//产品还没有生产,应该等待生产者先生产
try {
p.wait(); // 消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
案例二:
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println("消费元素: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
int num = random.nextInt(1000);
System.out.println("生产元素: " + num);
blockingQueue.put(num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
producer.start();
customer.join();
producer.join();
}