1.单例模式
1.1 什么是单例模式
单例模式是设计模式的一种,设计模式就是用来规范程序员代码的一种模板,使代码可控
而单例模式就是要保证只有一个实例对象的模式
1.2 怎么实现
单例模式有两个实现的方法分别为饿汉模式和懒汉模式
1.2.1 饿汉模式
就是直接在类里定义单例时就进行了初始化操作
public class SingletonHungry {
private static SingletonHungry singletonLazy = new SingletonHungry();
//构造方法私有化:为了返回相同的实例
private SingletonHungry(){}
//提供一个公开方法返回singletonLazy对象,为了让外界获取到singletonLazy
public static SingletonHungry getInstance(){
return singletonLazy;
}
}
这种写法是最简单的,但是一般在开发环境里,为了节约资源,我们有时候会想在要用的时候再创建实例,不想一开始就创建实例,为了让实例在使用时,才初始化于是就有了懒汉模式
1.2.2 懒汉模式,面试常考,需要会用手默写
就是先定义为空,不占据资源,等要用到的时候才创建
实现中必须要加锁,因为cpu调度的随机性,可能t1还没执行完就被调度到第二个里面了,导致出现了两个不同的实例
public class SingletonLazy {
private static SingletonLazy singletonLazy = null;
private SingletonLazy(){}
public static SingletonLazy getInstance() {
if(singletonLazy == null){//防止多次使用synchronized浪费cpu资源
synchronized (SingletonLazy.class) {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
}
}return singletonLazy;
}
}
关于synchronized的位置问题:synchronized要加在哪才能彻底解决呢
我们发现出现线程安全的主要是进行了多次的初始化操作
2.为什么要在前面再加一个判断语句呢
加锁/解锁是⼀件开销⽐较⾼的事情.⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候.因此 后续使⽤的时候,不必再进⾏加锁了
所以第一个是用来判断要不要加锁,省的浪费了资源
由于共享变量instance受到了改变,所以最好还加上volatile
2.阻塞队列
相当于在队列的基础上,只能装指定的数据,且队列满了如果继续入队列不会报错,只会等待出队列 如果队列空了,想继续出队也不会报错,只会等待入队列
如果队满了,还继续入队则后面的代码不会运行
public static void main(String[] args) throws InterruptedException {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue(3);
myBlockingQueue.put(1);
myBlockingQueue.put(2);
myBlockingQueue.put(3);
System.out.println("已经添加了三个元素");
myBlockingQueue.put(4);//开始阻塞
System.out.println("已经添加了四个元素");
System.out.println(myBlockingQueue.get());
System.out.println(myBlockingQueue.get());
System.out.println(myBlockingQueue.get());
System.out.println("已经取出了三个元素");
System.out.println(myBlockingQueue.get());
System.out.println("已经取出了四个元素");
}
如果将添加第四个元素那行注释掉
为了更好理解,我们来初步实现阻塞队列的一些功能
首先,写出一个基本的队列
1.定义存储数据的数组
2.定义头指针和尾指针,来入队和出队
3.定义元素数量
然后再在此基础上加入wait和notify,来实现阻塞功能
public class MyBlockingQueue {
//定义队列需要的数组
private Integer[] elementData;
//定义头尾下标
private volatile int head = 0;
private volatile int tail = 0;
//定义元素的个数
private volatile int size = 0;
public MyBlockingQueue(int copacity){//构造阻塞队列
if(copacity <= 0){
throw new RuntimeException("队列容量必须大于0.");
}
elementData = new Integer[copacity];
}
//插入数据的方法
public void put(Integer value) throws InterruptedException {
synchronized (this){
//判断是否满了
while(size >= elementData.length){//使用while循环是为了防止虚假等待
this.wait();
//阻塞队列在队列满时会进行阻塞等待
}
elementData[tail] = value;
tail++;
if(tail >= elementData.length){
tail = 0;
}
size++;
this.notifyAll();//用来通知get,告诉队列里有数据了能取了
}
//唤醒阻塞线程
}
//取出数据的方法
public synchronized Integer get() throws InterruptedException {
while (size == 0){
this.wait();
}
Integer value = elementData[head];
head++;
if(head >= elementData.length){
head = 0;
}
size--;
this.notifyAll();//用来给put进行通知,告诉有空位了可以入队了
return value;
}
}
2.1生产者消费者模型
public class Demo_703 {
public static void main(String[] args) {
// 定义一个阻塞队列, 交易场所
MyBlockingQueue queue = new MyBlockingQueue(100);
// 创建生产者线程
Thread producer = new Thread(() -> {
int num = 0;
// 使用循环不停的向队列中添加元素,直到队列容量占满
while (true) {
try {
// 添加元素
queue.put(num);
System.out.println("生产了元素:" + num);
num++;
// 休眠一会
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动
producer.start();
// 定义一个消费者线程
Thread consumer = new Thread(() -> {
// 不断的从队列中取出元素
while (true) {
try {
// 取元素
Integer value = queue.take();
System.out.println("消费了元素:" + value);
// 休眠一会
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动消费者线程
consumer.start();
}
}
生产者消费者模型就是利用了阻塞队列(消息队列)来在中间充当信息的部分,然后其他就是定义生产者线程和消费者线程即可
阻塞队列就是为了防止操作者再进行很多次的wait,notify操作而产生的