🎏🎏🎏个人主页🎏🎏🎏
🎏🎏🎏JavaEE专栏🎏🎏🎏
🎏🎏🎏上一篇文章:多线程(2)🎏🎏🎏
文章目录
- 1.单例模型
- 1.1概念
- 1.2如何保证只有一个对象
- 1.3两种单例模式
- 1.4两种模式的线程安全
- 1.4.1饿汉模式——线程安全
- 1.4.2懒汉模式——线程不安全
- 2.2.阻塞队列
- 2.1概念
- 2.2两种队列
- 2.2.1阻塞队列
- 2.2.2消息队列
- 2.3模拟实现阻塞队列
1.单例模型
1.1概念
单例模型——》单个实例,在整个进程中某一个类只有一个实例化对象(不会new出来多个对象)称为单例模型。
1.2如何保证只有一个对象
靠我们程序猿本身来保证,这样肯定是不现实的,所以需要编译器来帮我们来做一个强制的检查,通过一些编码上的技巧,使编译器可以自动发现我们的代码是否有多个对象,并且在尝试创建多个实例的时候,直接编译出错。
1.3两种单例模式
- 饿汉模式
在程序开始的时候就创建了对象,之后需要用到这个对象只需要调用这个对象即可,不需要再重新创建一个对象。为了防止创建出多个对象,可以利用代码的结构来操作比如创建以一个私有的构造方法。
//1.饿汉模式
class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
}
public class Deom22 {
public static void main(String[] args) {
Singleton.getInstance();
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
- 懒汉模式
相比饿汉模式的区别就是等程序需要用到这个对象的时候,才创建这个对象,这样操作的好处就是可以节省创建实例的成本以及避免一个项目中要用到多个单例模式,这样就要同时产生多个单个实例,会导致程序启动变慢。
//懒汉模式
class SinlgetonLazy {
private static SinlgetonLazy instance = null;
public static SinlgetonLazy getInstance() {
if(instance == null) {
instance = new SinlgetonLazy();
}
return instance;
}
private SinlgetonLazy() {
}
}
public class Deom23 {
public static void main(String[] args) {
SinlgetonLazy s1 = SinlgetonLazy.getInstance();
SinlgetonLazy s2 = SinlgetonLazy.getInstance();
System.out.println(s1 == s2);
}
}
1.4两种模式的线程安全
1.4.1饿汉模式——线程安全
原因:由于饿汉模式中的实例化对象是一开始就被创建了,比main线程调用还要早一些,其他线程比main线程还要慢,当其他线程来调用getInstance的时候,只是读取,并不是修改,多个线程同时读取一个变量属于线程安全。
1.4.2懒汉模式——线程不安全
原因:懒汉模式中的实例化对象是什么时候需要用就什么时候调用getIstance,多个线程同时去调用getIstance的时候,getIstance方法中的赋值是一个修改操作,此时就会发生多个线程对同一个变量进行修改操作就会引起线程不安全。
解决方法:加锁
class SinlgetonLazy1 {
public static volatile int count = 0;
private static SinlgetonLazy1 instance = null;
public static SinlgetonLazy1 getInstance() {
Object locker = new Object();
synchronized(locker) {
if (instance == null ) {
instance = new SinlgetonLazy1();
}
}
return instance;
}
private SinlgetonLazy1() {
}
}
public class Deom24 {
public static void main(String[] args) throws InterruptedException {
SinlgetonLazy1 s = SinlgetonLazy1.getInstance();
Thread t1 = new Thread(() -> {
SinlgetonLazy1 s1 = SinlgetonLazy1.getInstance();
});
Thread t2 = new Thread(() -> {
SinlgetonLazy1 s2 = SinlgetonLazy1.getInstance();
});
t1.start();
t2.start();
}
}
在上述代码中有一个缺陷的地方就是给加锁的时候其实只有在线程第一次调用的时候需要加锁,第二次就是读操作不是修改操作了,毕竟频繁加锁也是一个耗资源的操作,所以我们可以加一个判断来解决这个缺陷。
class SinlgetonLazy1 {
public static volatile int count = 0;
private static SinlgetonLazy1 instance = null;
public static SinlgetonLazy1 getInstance() {
Object locker = new Object();
if(instance == null) {
synchronized(locker) {
if (instance == null ) {
instance = new SinlgetonLazy1();
}
}
}
return instance;
}
private SinlgetonLazy1() {
}
}
public class Deom24 {
public static void main(String[] args) throws InterruptedException {
SinlgetonLazy1 s = SinlgetonLazy1.getInstance();
Thread t1 = new Thread(() -> {
SinlgetonLazy1 s1 = SinlgetonLazy1.getInstance();
});
Thread t2 = new Thread(() -> {
SinlgetonLazy1 s2 = SinlgetonLazy1.getInstance();
});
t1.start();
t2.start();
}
}
此处有两个if而且内容是一样的,但是表达的意义是不一样的,第一个if是判断线程是否需要加锁,第二个if是指是否创造对象。
2.2.阻塞队列
2.1概念
其实就是一个带有阻塞效果先进先出的队列,队列在生产者消费者模型充当一个媒介的身份。
2.2两种队列
2.2.1阻塞队列
队空的时候会阻塞和队满的时候会阻塞的先进先出的队列,在一个进程中直接使用阻塞队列实现生产者消费者模型
2.2.2消息队列
是一个带有标识的先进先出的队列,当让某一个类型的元素出的时候,其他类型的元素会阻塞,在分布式系统中使用单独部署的消息队列服务器实现生产者消费者模型。
2.3模拟实现阻塞队列
public class MyBlockingQueue {
public static int count;
public int rear;
public int front;
public int size = 0;
public int[] array;
public MyBlockingQueue(int Capacity) {
this.array = new int[Capacity];
}
public void put(int value) throws InterruptedException {
synchronized (this) {
while(size >= array.length) {
this.wait();
}
array[rear] = value;
rear = (rear+1) % array.length;
size++;
//唤醒take线程的阻塞
this.notify();
}
}
public int take() throws InterruptedException {
synchronized(this) {
while(size == 0) {
this.wait();
}
int ret = array[front];
front = (front+1) % array.length;
size--;
//唤醒put线程的阻塞
this.notify();
return ret;
}
}
//生产者消费者模型
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
//消费者1
Thread t1 = new Thread(() -> {
try {
while (true) {
Thread.sleep(1000);
myBlockingQueue.take();
System.out.println("消费者消费了一个:"+ count);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
//消费者2
Thread t2 = new Thread(() -> {
try {
while (true) {
Thread.sleep(1000);
myBlockingQueue.take();
System.out.println("消费者消费了一个:"+ count);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
//生产者
Thread t3 = new Thread(() -> {
try {
while(true) {
myBlockingQueue.put(count);
System.out.println("生产者生产一个:"+ count);
count++;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
//t1.start();
t2.start();
t3.start();
}
}
上述图片中,为什么选择while不选择if,原因在于:
当其他线程中有interrupt打断wait方法,则wait方法就会停止阻塞状态,继续往下执行throws操作,那么就会继续添加元素,那么就有可能会覆盖之前的元素,出现bug。而用while就会避免者种情况发生,while就会再一次判断是否满足条件从而就不会发生if出现的情况。
总结:在使用wait的时候最好搭配while不要搭配if。