多线程案例
1.单例模式
- 1.1懒汉
-
第一次使用的时候才会创建实例,所以叫懒汉
package classTest19; /* * 懒汉的方式实现单列模式*/ public class Test { private static Test instance = null; /* * 这个引用先初始化为null,而不是立即创建实例 *调用getInstance才会创建这个实例 */ public static Test getInstance() { if (instance ==null) { instance = new Test(); } return instance; } private Test() { //构造方法私有化 } public static void main(String[] args) { Test t1 = Test.getInstance(); Test t2 = Test.getInstance(); System.out.println(t1 == t2); } } - 代码分析
- 代码执行到Test t1的时候,就会创建实例,
- 执行到Test t2的时候,此时instance不为空,直接返回实例,
- 最后的结果就是为ture
- 同时这样的书写格式存在着一定的线程安全问题,不懂得可以参考上一篇博客思考,或者留言
- 对线程进行加锁
package classTest19;
/*
* 懒汉的方式实现单列模式*/
public class Test {
private static Object cocker = new Object();
private static Test instance = null;
/*
* 这个引用先初始化为null,而不是立即创建实例
*调用getInstance才会创建这个实例 */
public static Test getInstance() {
if(instance == null) {
/*
* 第一个if是判断是否要加锁
第二个if判断是否要创建对象
加两个if条件是因为 防止线程阻塞的时候
* 别的线程修改了instance的值*/
synchronized (cocker) {
if (instance == null) {
instance = new Test();
}
}
}
return instance;
}
private Test() {
//构造方法私有化
}
public static void main(String[] args) {
Test t1 = Test.getInstance();
Test t2 = Test.getInstance();
System.out.println(t1 == t2);
}
}
-
加锁仍然或存在一定问题 比如说堵塞等 可以使用volatile解决
-
指令重排序:调整代码的执行顺序,保证逻辑不变的前提下,提高程序的效率
- new操作可能涉及到三个步骤
- a 申请内存
- b 调用构造方法
- c 吧内存地址复赋值给引用
-
正常情况下是abc来执行,但是可能被优化为a c b 所以需要使用到volatile
-
volatile功能
- 保证内存可见性,每次访问量必须都要重新读取内存,而不会优化到寄存器中
- 禁止指令重排序,其修饰的变量读写相关的指令,是不能被重排序的
package classTest19;
/*
* 懒汉的方式实现单列模式*/
public class Test {
private static Object cocker = new Object();
private volatile static Test instance = null;
/*
比如 线程1判定instance指向一个非null的值,但是还未初始化对象 但是t2判定instance==null不成立
直接ruturn 就会出现问题 这就是重排序引发的问题
使用volatitle就不会出现重排序
* 这个引用先初始化为null,而不是立即创建实例
*调用getInstance才会创建这个实例 */
public static Test getInstance() {
if(instance == null) {
/*
* 第一个if是判断是否要加锁
第二个if判断是否要创建对象
加两个if条件是因为 防止线程阻塞的时候
* 别的线程修改了instance的值*/
synchronized (cocker) {
if (instance == null) {
instance = new Test();
}
}
}
return instance;
}
private Test() {
//构造方法私有化
}
public static void main(String[] args) {
Test t1 = Test.getInstance();
Test t2 = Test.getInstance();
System.out.println(t1 == t2);
}
}
总结
- 可能有的人会好奇懒汉设计模式有什么作用,以肯德基的疯狂星期四为例子,只有到了星期四这个模块才会出行,平时不会出现,这就为我们省去了很多不必要的开销。
- 1.2饿汉
package classTest17;
public class Singleton {
/*
* static 静态的 指的的是类的属性
* instance 就是Singleton类对象里面持有的属性
* 每个类的类对象只有一个,类中的static属性当然也只有一个
* instance指向的类对象就是唯一的对象
* 其他代码想要调用这个类的实例,只能通过这个get方法*/
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
//反映出单例模式
/*
* 实例在类加载的过程中就创建了 程序一启动就创建了
* 使用“饿汉” 来形容实例创建特别早,非常迫切*/
private Singleton() {
//其他代码无法进行new对象 避免出错
}
}
2.阻塞队列
-
线程安全的
-
满了的队列进行入队,此时就会阻塞,直到其他线程出队是才解除阻塞
-
空了的队列出队,此时就会阻塞,直到队列不空
-
基于阻塞队列 实现生产者消费者模型(一种多线程的方法)
-
解耦合(降低程序之间的依赖关系)
- 上述这个关系中,各个服务器之间依赖性就比较强,商户服务器挂了之后整个功能就不能实现了
- 生产者往队列中写入元素 消费者在队列中消费元素 后续添加模块只需要在队列中添加即可
- 削峰填谷
- 即使外界的请求出现峰值,由队列来承担 防止b c因为承受不住大量的请求从事挂了
- 生产者 --消费者模型(通过⼀个容器来解决⽣产者和消费者的强耦合问题)
package classTest21;
public class MyBlockintQueue {
private String[] elems = null;
private int head = 0;
private int tail = 0;
private int size = 0;
//数组容量
Object locker = new Object();
public MyBlockintQueue(int capacity) {
elems = new String[capacity];
}
//入队列
public void put (String elem) throws InterruptedException {
/*如何加锁?
* 加锁的过程中 如果不加if判断语句
* 此时线程1进入锁中,还没有执行完毕 size的长度不变
* 线程2等线程1解锁过后,已经跳过了判断条件
* 此时就会导致size比原来大,可能已经超过了数组的大小*/
synchronized (locker) {
/*使用循环 这样使用的时候可以多进行几次判定
* 如果是使用if 判定一次过后 进行阻塞 当线程被唤醒后不会在判断条件
* 但是此时可能线程被唤醒过后的条件并不满足
* 以下面为例 线程被唤醒的时候,可能此时线程还是满的
* 但是过了判定条件,就会出现问题
* 使用while可以避免 线程被其他线程唤醒时,可能此时的条件并不满足唤醒,由于已经过了if的判定
* 容易出现错误 使用while就可以循环判定 避免出错 */
while(size >= elems.length) {
//此时表示这个队列满了 后续需要这个代码进行阻塞
locker.wait();//此时队列进行阻塞 当有元素出队列的时候就不在阻塞
}
//新的元素要放到tail指向的位置上
elems[tail] = elem;
tail++;
if (tail >= elems.length) {
//此时又从第一个位置开始放
tail = 0;
}
size++;
//入队列成功后进行唤醒
locker.notify();
}
}
//出队列
public String take() throws InterruptedException {
synchronized (locker) {
while (size == 0) {
//队列为空 后续需要这个代码进行阻塞
//队列空了出队列,进行阻塞,直到入队列成功过后才不会阻塞
locker.wait();
}
//取出head位置的元素并返回
String elem = elems[head];
head++;
if (head >= elems.length) {
head = 0;
//循环 第一个位置开始
}
//取出过后,长度减一
size--;
//元素出队列过后唤醒元素
locker.notify();
return elem;
}
}
}
package classTest21;
/*
* 基于数组实现环形队列
* 先实现普通队列
* 加上线程安全
* 加上阻塞功能*/
public class Test {
public static void main(String[] args) {
MyBlockintQueue myBlockintQueue = new MyBlockintQueue(1000);
/* myBlockintQueue.put("qqq");
myBlockintQueue.put("qddq");
myBlockintQueue.put("qqffffq");
String elem = " ";
elem = myBlockintQueue.take();
System.out.println("elem:" + elem);*/
//生产者
Thread t1 = new Thread(() -> {
int n =1;
while (true) {
try {
myBlockintQueue.put(n +"");
System.out.println("生产元素" + n);
n++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//消费者
Thread t2 = new Thread(() -> {
while (true) {
try {
String n = myBlockintQueue.take();
System.out.println("消费元素" + n);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
结果:最后的结果为当生产者生产满过后,等消费者消费了再继续生产,很好的体现了削峰填谷的思想。