Java后端开发day46--多线程(二)
(以下内容全部来自上述课程)
多线程
1. Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
手动上锁、手动释放锁
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作 Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
2. 死锁(错误)
锁的嵌套容易导致死锁
3. 等待唤醒机制(生产者和消费者)
生产者消费者模式是一个十分经典的多线程协作的模式。
3.1 消费者:消费数据----消费者等待
- Desk:
package com.itheima.a13waitandnotify;public class Desk {/**作用:控制生产者和消费者的执行** *///是否有面条0:没有面条1:有面条 public static int foodFlag = 0;//总个数public static int count = 10;//锁对象public static 0bject lock = new 0bject();
}
- Foodie:
package com.itheima.a13waitandnotify;
public class Foodie extends Thread{@Overridepublic void run(){/** 1.循环* 2.同步代码块* 3.判断共享数据是否到了末尾(到了末尾)* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)**/while(true){synchronized (Desk.lock){if(Desk.count ==0){break;}else{//先判断桌子上是否有面条 if(Desk.foodFlag == 0){//如果没有,就等待 try{Desk.lock.wait();//让当前线程跟锁进行绑定} catch (InterruptedException e) {e.printStackTrace();} I }else{//把吃的总数-1 Desk.count--;//如果有,就开吃System.out.println("吃货在吃面条,还能再吃”+ Desk.count + "碗!!!");//吃完之后,唤醒厨师继续做 Desk. Lock.notifyA11();//修改桌子的状态 Desk.foodFLag =0;}}}}}}
3.2 生产者:生产数据----生产者等待
- Cook
package com.itheima.a13waitandnotify;
public class Cook extends Thread{@Overridepublic void run(){/** 1.循环*2.同步代码块* 3.判断共享数据是否到了末尾(到了末尾)* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)* */while (true){synchronized (Desk.Lock){if(Desk.count ==0){break;}else{//判断桌子上是否有食物 if(Desk.foodFlag == 1){//如果有,就等待 try {Desk.Lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{//如果没有,就制作食物System.out.println("厨师做了一碗面条");//修改桌子上的食物状态 Desk.foodFLag = 1;//叫醒等待的消费者开吃 Desk.ymck.notifyA11();}}}}}
}
- 测试类
package com.itheima.a13waitandnotify;public class ThreadDemo {public static void main(String[] args) {/*** 需求:完成生产者和消费者(等待唤醒机制)的代码 * 实现线程轮流交替执行的效果 ** *///创建线程的对象Cook c = new Cook();Foodie f = new Foodie();//给线程设置名字 c.setName("厨师"); f.setName("吃货");//开启线程 c.start(); f.start();}
}
3.3 阻塞队列方式实现
- TreadDemo:
package com.itheima.a14waitandnotify;import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {public static void main(String[] args){/*** 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码 细节:* 生产者和消费者必须使用同一个阻塞队列 ** *///1.创建阻塞队列的对象ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(capacity: 1);//2.创建线程的对象,并把阻塞队列传递过去 Cook c = new Cook(queue);Foodie f = new Foodie(queue);//3.开启线程 c.start(); f.start();}
}
- Cook:
package com.itheima.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断的把面条放到阻塞队列当中 try {queue.put( e: "面条");System.out.println("厨师放了一碗面条”)} catch (InterruptedException e) {e.printStackTrace();}}}
}
- Foodie:
package com.itheima.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断从阻塞队列中获取面条 try {String food = queue.take(); System.out.println(food);} catch (InterruptedException e) {e.printStackTrace();}}}
}
4. 线程的状态
Java中没有定义运行状态。
实际上:
5. 线程池
一个存放线程的容器。
5.1 主要核心原理
-
创建一个池子,池子中是空的
-
提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
-
但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
5.2 方法
public class MyThreadPoolDemo {public static void main(String[] args) {/*public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池 public static ExecutorService newFixedThreadPool (int nThreads)创建有上限的线程池*///1.获取线程池对象ExecutorService pool1= Executors.newCachedThreadPool();//2.提交任务pool1.submit(new MyRunnable()); pool1.submit(new MyRunnable());|//3.销毁线程池//pool1.shutdown();}
}
5.3 自定义线程池
队伍满了之后才会启用临时线程,直接执行没在队伍内的任务。
超出核心线程+临时线程+队伍长处加起来的和的任务,通常会触发任务拒绝策略。
/*ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数一:核心线程数量 不能小于0
参数二:最大线程数 不能小于0,最大数量>=核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为nu11
参数六:创建线程工厂 不能为nu11
参数七:任务的拒绝策略 不能为nu11 */ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize: 3, //核心线程数量,能小于0maximumPoolSize: 6, //最大线程数,不能小于0,最大数量>=核心线程数量 keepAliveTime: 60,//空闲线程最大存活时间 TimeUnit.SECONDS,//时间单位new ArrayBlockingQueue<>(capacity: 3),//任务队列 Executors.defaultThreadFactory(),//创建线程工厂new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
- 创建一个空的池子
- 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程
不断的提交任务,会有以下三个临界点:
- 当核心线程满时,再提交任务就会排队
- 当核心线程满,队伍满时,会创建临时线程
- 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
6.最大并行数
与CPU有关
4核8线程:
- 4核:4个大脑,可以同时干4件事 -->超线程技术 --> 8线程
- 8线程:最大并行数为8
7. 线程池多大合适
- CPU密集型运算:计算比较多,读取本地文件或数据库较少 --> 最大并行数+1
- I/O密集型运算:读取本地文件或数据库比较多(大多数都是此类)
公式举例: