文章目录
在并发中,有一些经典的模式,我们可以学习下
生产者与消费者模式(线程同步,通信)
面试时,有可能要手写
生产这与消费者模式
- 可以使用原生的
Object.wait()与Object.notify
方法- 可以使用
ReentranLock生成的 Condition 进行Condition.await()与Condition.signal
方法- 在JUC包中,使用到了
ArrayBlockingQueue
也使用到了生产者与消费者模式
下面进行剖析
生产者消费者模式主要角色分析:生产者:用于提交用户请求,提取用户任务,并装入内存缓冲区
消费者:在内存缓冲区中提取并处理任务
内存缓冲区:缓存生产者提交的任务或数据,供消费者使用
任务:生产者向内存缓冲区中提交的数据结构
Main:使用生产者和消费者的客户端
生产者消费者编写的时候抓住:判断等待
,业务逻辑
,通知
下面以一个线程加1,一个线程减1的生产者消费者模式进行分析
Object方式实现生产者消费者模式
package com.zj.ProductAndConsumer;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/30 0030 20:57
*/
public class Case1 {
public static void main(String[] args) {
Data data1 = new Data();
new Thread(new Runnable() {
@Override
public void run() {
try {
while(true)
data1.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
while(true)
data1.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
// 判断等待,业务,通知
class Data{ // 数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
while (number!=0){ //0
// 等待
this.wait();
}
//业务
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number==0){ // 1
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
this.notifyAll();
}
}
ReentranLock实现生产者与消费者
package com.zj.ProductAndConsumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/30 0030 21:04
*/
public class Case2 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
data.decrement();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
data.increatment();
}
}
}).start();
}
}
class Data2{
private int num = 0;
//用ReentranLock取代Synchronized
private ReentrantLock lock = new ReentrantLock();
//用Condition绑定这个lock
Condition condition = lock.newCondition();
//当数据为0的时候进行加1(业务逻辑判断)
//对数据加1
//通知
public void increatment() {
lock.lock();
//逻辑判断
try {
while(num==1){
condition.await();
}
//业务处理
num++;
System.out.println(Thread.currentThread().getName()+"--"+num);
//唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() {
//逻辑判断
lock.lock();
//逻辑判断
try {
while(num==0){
condition.await();
}
//业务处理
num--;
System.out.println(Thread.currentThread().getName()+"--"+num);
//唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
单例模式(并发中如何实现安全的并发)
面试的时候可能要手写单例模式,下面进行了总结;单例模式的核心是如何确保创建对象时是线程安全的
饿汉式
私有化构造器
类初始化时,立即加载对象
提供获取对象的方法
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:23
*
* 饿汉式单例
*
* 1、构造器私有化
* 2、上来创建一个静态对象
* 3、一个方法返回创建的静态对象
*/
public class SingleDemon01 {
//1、构造器私有化
private SingleDemon01(){
}
//思考为什么是静态对象?
//2、类初始化时,立即加载对象:
//这一步和JVM类记载有关,jvm保证静态变量在类加载的时候是线程安全的
//JVM类加载过程: 加载 验证 准备 解析 初始化
private static SingleDemon01 singleDemon01= new SingleDemon01();
//3、提供获取对象的方法,没有synchrionized,效率高
public static SingleDemon01 getInstance(){
return singleDemon01;
}
}
懒汉式
私有化构造器
类初始化不用立即加载对象
提供获取对象的方法,需要synchronized,效率低
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:33
* 饿汉式
*/
public class SingleDemon02 {
//1、构造器私有化
private SingleDemon02(){
}
//思考为什么是静态对象?
//2、类初始化时,不立即加载对象:
private static SingleDemon02 instance= null;
//3、提供获取对象的方法,有synchrionized,效率低
public static synchronized SingleDemon02 getInstance(){
if(instance==null){
instance = new SingleDemon02();
}
return instance;
}
}
DCL(double checking )
构造器私有化
类初始化是,不用立即加载对象
提供获取对象的方法,而不是在方法内部进行双重检测
为了避免指令重拍,可以采用volitale关键字
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:38
*
* 双重检测懒汉式:(DCL)_
*/
public class SingleDemon03 {
//1、构造器私有化
private SingleDemon03(){
}
//思考为什么是静态对象?
//2、类初始化时,不立即加载对象:
//为了避免指令重拍:可以在变量前加上 volitale关键字
private static volitale SingleDemon03 instance= null;
//3、提供获取对象的方法,双重检测
public static SingleDemon03 getInstance(){
if(instance==null){
synchronized (SingleDemon03.class) {
if(instance==null)
instance = new SingleDemon03();
}
}
return instance;
}
}
枚举单例(线程安全,调用效率高,不能延时加载)推荐
package GOF23.Singleton;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/3/1 0001 14:56
* 枚举
*/
public enum SingleDemon05 {
//枚举里纯天然就是单例的
INSTANCE;
public SingleDemon05 getInstance(){
return INSTANCE;
}
}
不变模式(这是绝对安全的并发)
介绍
不变模式就是多线程友好的,它的核心思想是,一个对象一旦被创建,则它的内部状态将永远不会发生改变。所以没有一个线程可以修改其内部状态和数据,同时其内部状态也绝不会自行发生改变。基于这些特性,对不变对象的多线程操作不需要进行同步控制
如何实现
在Java语言中,不变模式的实现很简单,为确保对象被创建后,不发生任何改变,并保证不变模式正常工作,只需要注意以下4点:
- 去除setter方法以及所有修改自身属性的方法
- 将所有属性设置为私有,并用final标记,确定其不可修改
- 确保没有子类可以重载修改它的行为
- 有一个可以创建完整对象的构造函数
Jdk中的实现
不变模式通过回避问题而不是解决问题的态度来处理多线程并发访问控制。不变对象是不需要进行同步操作的。由于并发同步会对性能产生不良影响,因此,在需求允许的情况下,不变模式可以提高系统的并发性能和并发量
Future模式(异步模式)