多线程案例
单例模式
单例模式是一种设计模式。
写代码时有些常见场景,设计模式就是针对这些常见场景给出的解决方案。
举例:
中午这顿饭,使用了4个碗,吃完之后,立即把这4个碗洗了【饿汉模式】 饿:着急
中午这顿饭,使用了4个碗,吃完之后,先不洗,如果晚饭只需要2个碗,就只洗2个碗即可【懒汉模式】
饿汉模式
static 修饰的成员更准确的说,应该叫“类成员”、“类属性、类方法”。不加static修饰的方法,叫做“实例成员”、“实例属性、实例方法”。
一个Java程序中,一个类对象只存在一份(JVM保证的),进一步的也就保证了类的static成员也是只有一份的。
类对象(类名.class)
class Singleton{
//1.使用static创建一个实例,并且立即进行实例化
//这个instance对应的实例,就是该类的唯一实例
private static Singleton instance=new Singleton();
//2.为了防止程序员在其他地方不小心new这个singleton,就可以把构造方法设为private
private Singleton(){}
//3.提供一个方法,让外面的能够拿到唯一的实例
public static Singleton getInstance(){
return instance;
}
}
public class Demo19 {
public static void main(String[] args) {
Singleton instance=Singleton.getInstance();
}
}
懒汉模式【经典面试题】
真正要解决的问题,是实现一个线程安全的单例模式
线程安不安全,具体指的是多线程环境下,并发的调用getInstance方法,是否可能产生bug
那么懒汉模式怎样保证线程安全呢?
加锁【使用类对象作为锁对象,类对象在一个程序中只有唯一的一份,就能保证多个线程调用getInstance的时候都是针对同一个对象进行加锁的】
虽然加锁之后,线程安全问题,得到解决了,但又有了新的问题。
对于刚才这个懒汉模式的代码来说,线程不安全,是发生在instance被初始化之前,未初始化的时候,多线程调用getInstance,就可能同时涉及到读和修改。
但是一旦instance被初始化之后(一定不是null,if条件,就不成立了),getInstance 操作就只剩下两个读操作,也就线程安全了。
但是按照上述加锁方式,无论代码是否初始化之后,还是初始化之前,每次调用getInstance 都会进行加锁,也就意味着即使是初始化之后(已经线程安全了),但是仍然存在大量的锁竞争,但是实际这样的竞争是没必要的!加锁确实能让代码保证线程安全,但也付出了代价(程序的速度就慢了)
public static Singleton2 getInstance(){
//并不是代码中有synchronized就一定线程安全,synchronized加的位置也得正确
//使用类对象作为锁对象(类对象在一个程序中,只有唯一的一份,就能保障多个线程调用getInstance的时候就是针对同一个对象进行加锁的)
//如果这个条件成立,说明当前的单例模式未初始化,存在线程安全风险,就需要加锁
if(instance ==null){
synchronized (Singleton2.class){
if(instance==null){
instance= new Singleton2();
}
}
}
return instance;
}
最终的代码:
class Singleton2{
//1.懒汉模式就不是立即初始化实例
private static volatile Singleton2 instance=null;
//2.把构造方法设为private
private Singleton2(){}
//3.提供一个方法来获取到上述单例的实例
//只有当真正用到这个实例的时候,才会真正去创建这个实例
public static Singleton2 getInstance(){
//并不是代码中有synchronized就一定线程安全,synchronized加的位置也得正确
//使用类对象作为锁对象(类对象在一个程序中,只有唯一的一份,就能保障多个线程调用getInstance的时候就是针对同一个对象进行加锁的)
//如果这个条件成立,说明当前的单例模式未初始化,存在线程安全风险,就需要加锁
if(instance ==null){
synchronized (Singleton2.class){
if(instance==null){
instance= new Singleton2();
}
}
}
return instance;
}
}
public class Demo20 {
public static void main(String[] args) {
Singleton2 instance =Singleton2.getInstance();
}
}
关键点
1.正确的加锁位置
2.双重 if 判定
3.volatile
阻塞队列
队列:先进先出
阻塞队列同样也是一个符合先进先出规则的队列
相比于普通队列,阻塞队列又有一些其他方面的功能
1.线程安全
2.产生阻塞效果
1)如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止。
2)如果队列为满,尝试入队列,也会出现阻塞,阻塞到队列不为满为止。
基于上述特性,就可以实现“生产者消费者模型”。
“生产者消费者模型”是日常开发中处理多线程问题的一个典型方式。
举例:
擀饺子皮 + 包饺子
1)ABC三个人分别每个人都是先擀一个皮,然后包一个饺子(存在一个问题,锁冲突比较激烈【擀面杖只有一个】)
2)A专门负责擀饺子皮,B/C专门负责包饺子
此时A就是饺子皮的生产者,要不断的生产一些饺子皮
B C 就是饺子皮的消费者,要不断的使用 / 消耗饺子皮
对于放饺子的东西就是“交易场所”。
此时的阻塞队列就可以作为生产者消费者模型中的交易场所
java标准库中阻塞队列的用法
public class Demo21 {
public static void main(String[] args) throws InterruptedException {
BlockingDeque<String > queue=new LinkedBlockingDeque<>();
queue.put("hello");
String s=queue.take();
}
}
自己实现阻塞队列
队列可以是基于数组实现,也可以是基于链表实现
此处基于数组实现阻塞队列更简单。
class MyBlockingQueue{
//保存数据本体
private int[] data=new int[100];
//有效元素个数
private int size=0;
//队首下标
private int head=0;
//队尾下标
private int tail=0;
//专门的锁对象
private Object locker=new Object();
//入队
//对于put操作,阻塞条件是队列为满
public void put(int value) throws InterruptedException {
synchronized (locker){
if(size==data.length){
locker.wait();
}
data[tail]=value;
tail++;
if(tail >= data.length){
tail=0; // tail=tail % data.length
}
size++;
//如果入队列成功,则队列非空,于是就唤醒take中的阻塞等待
locker.notify();
}
}
//出队列
public Integer take() throws InterruptedException {
synchronized (locker){
if(size==0){
//如果队列为空,就返回一个一个非法值
// return null;
locker.wait();
}
int ret=data[head];
head++;
if(head >= data.length){
head=0;
}
size--;
//take 成功之后,就唤醒put中的等待
locker.notify();
return ret;
}
}
}
public class Demo22 {
private static MyBlockingQueue queue=new MyBlockingQueue();
public static void main(String[] args) {
//实现一个简单的生产者消费者模型
Thread producer=new Thread(() ->{
int num=0;
while(true){
try {
System.out.println("生产了:"+ num);
queue.put(num);
num++;
//当生产者生产的慢一些的时候,消费者就得跟着生产者的步伐走
//Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
Thread customer =new Thread(() ->{
while(true){
try {
int num=queue.take();
System.out.println("消费了:"+num);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
}
}