一.生产者消费者问题
示例:
- 面包店
- 10个生产者,每个每次生产3个
- 20个消费者,每个每次消费一个
- 进阶版需求
- 面包师傅每个最多生产30次,面包店每天生产10303=900个面包
- 消费者也不是一直消费。把900个面包消费完结束
- 隐藏信息:面包店每天生产面包的最大数量为900个
-
消费者把900个面包消费完结束
代码示例:
/**
* 面包店
* 10个生产者,每个每次生产3个
* 20个消费者,每个每次消费一个
*
* 进阶版需求
* 面包师傅每个最多生产30次,面包店每天生产10*30*3=900个面包
* 消费者也不是一直消费。把900个面包消费完结束
*
* 隐藏信息:面包店每天生产面包的最大数量为900个
* 消费者把900个面包消费完结束
*/
public class AdvancedBreadShop {
//面包店库存数
private static int COUNT;
//面包店生产面包的总数,不会消费的
private static int PRODUCE_NUMBER;
public static class Consumer implements Runnable{
private String name;
public Consumer(String name) {
this.name = name;
}
@Override
public void run() {
try {
while (true){
synchronized (AdvancedBreadShop.class){
if(PRODUCE_NUMBER==900&&COUNT==0){
System.out.println("今天面包已经卖完了");
break;
}else {
if(COUNT==0){
AdvancedBreadShop.class.wait();
}else {
System.out.printf("%s消费了一个面包\n",this.name);
COUNT--;
AdvancedBreadShop.class.notifyAll();
Thread.sleep(100);
}
}
}
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Producer implements Runnable{
private String name;
public Producer(String name) {
this.name = name;
}
@Override
public void run() {
try {
//生产者生产30次,结束循环
for(int i=0;i<=30;i++) {
synchronized (AdvancedBreadShop.class){
if(i==30){
System.out.println("今天面包生产完了");
break;
}else {
if(COUNT>97){
AdvancedBreadShop.class.wait();
}else {
COUNT=COUNT+3;
PRODUCE_NUMBER=PRODUCE_NUMBER+3;
System.out.printf("%s生产了三个面包\n",this.name);
AdvancedBreadShop.class.notifyAll();
Thread.sleep(100);
}
}
}
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread[] Consumers=new Thread[20];
Thread[] Producers=new Thread[10];
for (int i = 0; i <20 ; i++) {
Consumers[i]=new Thread(new Consumer(String.valueOf(i)));
}
for (int i = 0; i <10 ; i++) {
Producers[i]=new Thread(new Producer(String.valueOf(i)));
}
for (int i = 0; i <20 ; i++) {
Consumers[i].start();
}
for (int i = 0; i <10 ; i++) {
Producers[i].start();
}
}
}
二.单例模式
基于单例模式下的懒汉模式(双重校验锁实现)(多线程版,二次判断,效率高)
代码示例:
public class Singleton {
//volatile关键字修饰,保证的可见性和代码的顺序性
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
//判断instance是否为空,竞争锁的条件
if (instance == null) {
//保证线程安全,为Singleton.class加锁
synchronized (Singleton.class) {
//再次判断instance是否为空,防止多个线程进入第一个if后
//对synchronized锁竞争失败进入阻塞状态后,再次进入运行态时
//new了多个Singleton,不符合单例模式
//保证线程安全
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
三.阻塞式队列
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
阻塞式队列代码实现:
/**
* 实现阻塞队列
* 1.线程安全问题:在多线程情况下,put,take不具有原子性,4个属性,不具有可见性
* 2.put操作:如果存满了,需要阻塞等待。take操作:如果是空,阻塞等待
* @param <T>
*/
public class MyBlockingQueue <T>{
//使用数组实现循环队列
private Object[] queue;
//存放元素的索引
private int putIndex ;
//取元素的索引
private int takeIndex;
//当前存放元素的数量
private int size;
public MyBlockingQueue(int len){
queue=new Object[len];
}
//存放元素,需要考虑:
//1.putIndex超过数组长度
//2.size达到数组最大长度
public synchronized void put(T e) throws InterruptedException {
//不满足执行条件时,一直阻塞等待
//当阻塞等待都被唤醒并再次竞争成功对象锁,回复往下执行时,条件可能被其他线程修改
while (size==queue.length){
this.wait();
}
//存放到数组中放元素的索引位置
queue[putIndex]=e;
putIndex=(putIndex+1)%queue.length;
size++;
notifyAll();
}
//取元素
public synchronized T take() throws InterruptedException {
while (size==0){
this.wait();
}
T t= (T) queue[takeIndex];
queue[takeIndex]=null;
takeIndex=(takeIndex+1)%queue.length;
size--;
notifyAll();
return t;
}
public int size(){
return size;
}
public static void main(String[] args) {
MyBlockingQueue<Integer>queue=new MyBlockingQueue<>(10);
//多线程的调试方式:1.写打印语句 2.jconsole
for (int i = 0; i <3 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int j = 0; j <100 ; j++) {
queue.put(j);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
for (int i = 0; i <3 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true){
int t= queue.take();
System.out.println(Thread.currentThread().getName()+":"+t);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
四.线程池
线程池最大的好处就是减少每次启动、销毁线程的损耗
import java.util.concurrent.*;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
//以快递公司,快递员,快递业务为模型
ThreadPoolExecutor pool=new ThreadPoolExecutor(
5,//核心线程数---->正式员工数
10,//最大线程数-->正式员工+临时员工
60,//临时工的最大等待时间
TimeUnit.SECONDS,//idle线程的空闲时间-->临时工最大的存活时间,超过就解雇
new LinkedBlockingQueue<>(),//阻塞队列,任务存放的地方--->快递仓库
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(new Runnable() {
@Override
public void run() {
//r对象是线程池内部封装过的工作任务类(Worker),会一直循环等待的方式从阻塞队列中拿取任务并执行
//所以不能调用r.run();方法
System.out.println(Thread.currentThread().getName()+"开始执行了");
}
});
}
},//创建线程的工厂类 线程池创建线程时,调用该工厂类的方法创建线程(满足该工厂创建线程的要求)
//---->对应招聘员工的标准
/**
* 拒绝策略:达到最大线程数且阻塞队列已满,采取拒绝策略
* AbortPolicy:直接抛出RejectedExecutionException(不提供handler时的默认策略)
* CallerRunsPolicy:谁(某个线程)交给我(线程池)的任务,我拒绝执行,由谁自己去执行
* DiscardPolicy:交给我的任务直接丢弃掉
* DiscardOldestPolicy:阻塞队列中最旧的任务丢弃
*/
new ThreadPoolExecutor.AbortPolicy()//拒绝策略-->达到最大线程数,且阻塞队列已满,采取的拒绝策略
);//线程池创建以后,只要有任务们就会自动执行
for (int i = 0; i <20 ; i++) {
//线程池执行任务:execute方法,submit方法--->提交执行一个任务
//区别:返回值不同
pool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//线程池有4个快捷的创建方式(实际工作不使用,作为面试了解)
//实际工作需要使用ThreadPoolExecutor,构造参数是我们自己指定,比较灵活
ExecutorService pool2=Executors.newSingleThreadExecutor();//创建单线程池
ExecutorService pool3=Executors.newCachedThreadPool();//缓存的线程池
ExecutorService pool5=Executors.newFixedThreadPool(4);//固定大小线程池
ScheduledExecutorService pool4=Executors.newScheduledThreadPool(4);//计划任务线程池
//两秒中之后执行这个任务
pool4.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}, 2, TimeUnit.SECONDS);
//一直执行任务
pool4.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}, 2, 1,TimeUnit.SECONDS);//比如一个脑子,两秒后开始叫我,然后每隔一秒叫我一次
}
}