- 单例模式
- 饿汉模式
- 懒汉模式
- 阻塞队列
- 定时器
- 线程池
这是一种常见的“设计模式”,当在某些情况下,不需要多个实例,就可以使用单例模式来解决,如果想要尝试创建多个实例,代码就会报错。
可以有两种方式:
饿汉模式:当类加载的时候就会立刻实例化。
懒汉模式:类加载时不会实例化,当第一次调用这个类的时候才会去实例化。(可以仔细拼一下为啥叫这两个名字,一个急得想马上创建,一个等啊等啊不到万不得已需要使用了才实例化)
类加载的时候就会立刻实例化
static class Singleton{
//这里的构造方法使用private,在类外就无法new这个实例了
private Singleton() {
}
//这里使用static,表示和实例无关,只与类有关
private static Singleton instance = new Singleton();
//gerInstance是获取实例的唯一方法
public static Singleton gerInstance() {
return instance;
}
}
public static void main(String[] args) {
Singleton s1 = Singleton.gerInstance();
Singleton s2 = Singleton.gerInstance();
System.out.println(s1 == s2);
}
在main方法中,看起来是创建了两个实例,但是我们的打印结果是true,说明这两个实例其实是一个。
懒汉模式类加载的时候不会实例,只有在第一次调用这个类的时候才会去实例化
static class Singleton {
private Singleton() {
}
private static Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
可以观察一下这个代码,当多线程同时调用getInstance方法时,是需要分成几步的。读取instance中的内容,判断是否为null,如果为null,就new实例。返回实例的地址。根据线程安全的思想,可以看出这个代码会导致线程不安全。
1、给getInstance加锁,保证原子性
static class Singleton {
private Singleton() {
}
private static Singleton instance = null;
public static Singleton getInstance() {//也可以把锁加在这一行,只不过是锁粒度大一点
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
仔细分析,可以发现,如果把实例化已经创建好之后,再去调用getInstance就属于线程安全了。只有在实例化之前是线程不安全的。就可以只在实例化之前加锁,实例化之后就可以不用加锁了,这样就降低了锁的粒度
2、再次进行判断,降低锁粒度
static class Singleton {
private Singleton() {
}
private static Singleton instance = null;
public static Singleton getInstance() {//也可以把锁加在这一行,只不过是锁粒度大一点
if (instance == null) {//这个判断是为了只在实例化之前调用加锁,降低锁的粒度
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这个代码还存在一点线程不安全的问题,多线程在调用getInstance,先加锁的线程在修改instance,而后加锁的线程在读数据,这样就会存在内存可见性的安全问题
3、加上volatile,解决内存可见行的问题。
static class Singleton {
private Singleton() {
}
private volatile static Singleton instance = null;
public static Singleton getInstance() {//也可以把锁加在这一行,只不过是锁粒度大一点
if (instance == null) {//这个判断是为了只在实例化之前调用加锁,降低锁的粒度
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
阻塞队列
这是并发变成的一个重要的组件,可以帮助实现“生产者-消费者模型”。生产者和消费者之间是不能直接联系的,需要通过一个阻塞队列来通信。生产者生产元素,直接放进阻塞队列中,消费者也直接从阻塞队列中取元素。
如果生产者生产的快了,阻塞队列满了,继续生产元素,就会阻塞队列,一直到有消费者来消费元素,才能继续去生产。
如果消费者消费的快了,阻塞队列就会空,继续消费元素,也会阻塞队列,一直到有生产者生产元素,消费者才能去消费。
这样来平衡生产者和消费者
1、先实现一个基础的队列操作,阻塞队列只需要入队列和出队列操作。
static class BlockingQueue{
private int[] array = new int[100];//使用数组来实现循环队列
private int head = 0;//头节点下标
private int tail = 0;//尾节点下标
private int size = 0;//数组元素的实例个数
//入队列
public void put(int value) {
array[tail] = value;
tail++;
if(tail == array.length) {
tail = 0;
}
size++;
}
//出队列
public int take() {
int ret = array[head];
head++;
if(head == array.length) {
head = 0;
}
size--;
return ret;
}
}
2、需要保证操作的原子性,加锁
static class BlockingQueue{
private int[] array = new int[100];//使用数组来实现循环队列
private int head = 0;//头节点下标
private int tail = 0;//尾节点下标
private int size = 0;//数组元素的实例个数
//入队列
public void put(int value) {
synchronized (this) {
array[tail] = value;
tail++;
if(tail == array.length) {
tail = 0;
}
size++;
}
}
//出队列
public int take() {
int ret;
synchronized (this) {
ret = array[head];
head++;
if(head == array.length) {
head = 0;
}
size--;
}
return ret;
}
}
3、实现阻塞队列的特点,加上wait(),notify()。
入队列操作如果满了,就需要等待,直到有出队列的元素之后才能继续入队列。出队列操作如果空了,也需要等待,直到有入队列的元素之后才能继续出队列。(使用wait()操作时,如果需要用if,建议可以改成while)
static class BlockingQueue{
private int[] array = new int[100];//使用数组来实现循环队列
private int head = 0;//头节点下标
private int tail = 0;//尾节点下标
private int size = 0;//数组元素的实例个数
//入队列
public void put(int value) throws InterruptedException {
synchronized (this) {
while (size == array.length) {
wait();
}
array[tail] = value;
tail++;
if(tail == array.length) {
tail = 0;
}
size++;
notify();
}
}
//出队列
public int take() throws InterruptedException {
int ret;
synchronized (this) {
while (size == 0) {
wait();
}
ret = array[head];
head++;
if(head == array.length) {
head = 0;
}
size--;
notify();
}
return ret;
}
}
4、入队列和出队列操作都会去获取size的值,会出现内存可见性的问题,加上volatile。(为了安全起见,我给三个变量都加上了volatile)
//阻塞队列
static class BlockingQueue{
private int[] array = new int[100];//使用数组来实现循环队列
private volatile int head = 0;//头节点下标
private volatile int tail = 0;//尾节点下标
private volatile int size = 0;//数组元素的实例个数
//入队列
public void put(int value) throws InterruptedException {
synchronized (this) {
while (size == array.length) {
wait();
}
array[tail] = value;
tail++;
if(tail == array.length) {
tail = 0;
}
size++;
notify();
}
}
//出队列
public int take() throws InterruptedException {
int ret;
synchronized (this) {
while (size == 0) {
wait();
}
ret = array[head];
head++;
if(head == array.length) {
head = 0;
}
size--;
notify();
}
return ret;
}
}
定时器
在某些业务逻辑中,我们并不需我有些逻辑立刻就执行,需要让他等待一段时间之后再去执行。
定时器的构成:
1、需要一个类来描述这个类需要干什么,以及记录这个类应该什么时间来执行。
2、需要一个阻塞优先队列,来组织很多个任务。
3、需要一个扫描线程,循环检测队列中的任务是否需要执行。
4、需要一个方法,将所有的任务安排到队列中
class Task implements Comparable线程池{ private Runnable command;//借助run方法来具体描述任务 private Long time; public Task(Runnable command,Long after) { this.command = command; this.time = System.currentTimeMillis() + after; } public void run() { command.run(); } @Override public int compareTo(Task o) { return (int) (this.time - o.time); } } class Worker extends Thread { private PriorityBlockingQueue queue = new PriorityBlockingQueue<>(); private Object mailBox; public Worker(PriorityBlockingQueue queue,Object mailBox) { this.queue = queue; this.mailBox = mailBox; } @Override public void run() { while (true) { try { Task task = queue.take(); Long time = System.currentTimeMillis(); if(task.time > time) { //时间没到再把任务放回到队列中 queue.put(task); synchronized (mailBox) { mailBox.wait(task.time - time); } }else { //时间到了运行这个任务 task.run(); } } catch (InterruptedException e) { e.printStackTrace(); break; } } } } class Timer { private Object mailBox = null; //1、构建一个类来描述这段逻辑(使用Runnable中的run方法) //2、使用阻塞优先队列来组织这些任务 private PriorityBlockingQueue queue = new PriorityBlockingQueue (); //3、使用一个线程取出队首元素,查看是否到时间 public Timer (){ Worker worker = new Worker(queue,mailBox); worker.start(); } //4、需要一个接口,将这些任务放入安排起来 public void schedule(Runnable command,Long after) { Task task = new Task(command,after); queue.put(task); synchronized (mailBox) { mailBox.notify(); } } }
在一个项目中会使用到很多的线程,一直的创建和销毁线程会有很大的开销,我们可以使用线程池来避免这些开销。线程池中包含了一些线程,我们可以直接去使用。
主要有两个核心操作:
1、execute:把任务加入到线程池中
2、shotDown:销毁线程池中的所有线程
线程池的构成:
1、需要一个类来表示工作的线程
2、需要一个类来表示线程的执行任务(借助Runnable中的run方法)
3、使用阻塞队列来组织若干个任务
4、使用顺序表来组织若干个线程
class Worker extends Thread{
private BlockingQueue queue = null;
public Worker(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
//try包裹while语句只要出现任何的异常就会立刻退出
try {
while (!Thread.currentThread().isInterrupted()) {
Runnable command = (Runnable) queue.take();
command.run();
}
} catch (InterruptedException e) {
System.out.println("线程被终止");
e.printStackTrace();
}
}
}
class MyThreadPool{
//使用阻塞队列来组织若干个任务
private BlockingQueue queue = new linkedBlockingDeque<>();
//使用List来组织若干个线程
private List workers = new ArrayList<>();
private static final int maxWorkerCount = 10;
//把一个任务加到线程池中
public void execute(Runnable runnable) throws InterruptedException {
//如果线程池中数量少,就创建新线程。如果多了就不创建
if(queue.size() < maxWorkerCount) {
Worker worker = new Worker(queue);
worker.start();
workers.add(worker);
}
queue.put(runnable);
}
//销毁线程池中的所有线程
public void shotDown() throws InterruptedException {
for (Worker worker : workers) {
worker.interrupt();
}
for (Worker worker : workers) {
worker.join();
}
}
}



