目录
一、为什么要有多线程?
1、线程与进程
2、多线程的应用场景
3、小结编辑
二、多线程中的两个概念(并发和并行)
1、并发
2、并行
3、小结
三、多线程的三种实现方式
1、继承Thread类的方式进行实现
2、实现Runnable接口的方式进行实现
3、利用Callable接口和Future接口方式的实现
4、多线程三种实现方式对比
四、常见的成员方法
1、get/setName方法 -- 线程名字
2、currentThread方法 -- 获取当前线程对象
3、sleep方法 -- 线程休眠
4、set/getPriority方法 -- 线程优先级
5、setDaemon方法 -- 守护线程
6、yield方法 -- 礼让线程
7、join方法 -- 插入线程
8、线程的生命周期
五、线程安全的问题
1、练习:设计一个程序模拟电影院卖票
2、买票引发的安全问题
2.1、重复票的由来:(线程在执行代码的过程中,CPU的执行权随时有可能被抢走)
2.2、出现了超出范围的票:(和上面的原因相同)
3、安全问题的解决办法 -- 同步代码块
4、同步代码块中的两个小细节
4.1、细节1:synchronized要写在循环的里面编辑
4.2、细节2:synchronized中的锁对象一定是唯一的
5、同步方法
6、StringBuilder和StringBuffer的区别
7、Lock锁(手动加锁、释放锁)编辑
7.1、Lock使用不规范造成的两个安全问题
六、死锁
七、生产者和消费者(等待唤醒机制)
1、消费者等待
2、生产者等待编辑
3、常见方法(wait/notify/notifyAll)
4、消费者与生产者代码实现
4.1、Cook.java
4.2、Desk.java
4.3、Foodie.java
4.4、ThreadDemo.java
5、阻塞队列方式(另一种等待唤醒机制)编辑
5.1、阻塞队列的继承结构编辑
5.2、阻塞队列实现等待唤醒机制
7、多线程的6中状态
八、综合练习
1、多线程练习1(卖电影票)编辑
2、多线程练习2(送礼品)
3、多线程练习3(打印奇数数字)编辑
4、多线程练习4(抢红包)
5、多线程练习5(抽奖箱抽奖)
6、多线程练习6(多线程统计并求最大值)
7、多线程练习7(多线程之间的比较)
8、多线程练习8(多线程阶段大作业)
九、线程池
1、吃饭买碗的故事
1.1、问题编辑
1.2、解决方案
2、以前写多线程的弊端编辑
3、线程池的核心原理
4、线程池的代码实现
4.1、Executors工具类编辑
4.2、线程复用示例
4.3、创建一个有上限的线程池
5、自定义线程池(ThreadPoolExecutor)
5.1、任务拒绝策略编辑
5.2、代码实现编辑
5.3、小结编辑
6、最大并行数
6.1、什么是最大并行数?编辑
6.2、向Java虚拟机返回可用处理器的数目
7、线程池多大才合适?
编辑
十、多线程的额外扩展内容准备面试时可以再突击学习,资料可见《多线程(额外扩展).md》
一、为什么要有多线程?
1、线程与进程
进程:进程是程序的基本执行实体
举例:在任务管理器中,一个软件运行之后,它就是一个进程
线程:(简单理解,线程就说应用软件中互相独立,可以同时运行的功能)
单线程程序:所有的都在一个线程中执行,耗时长
2、多线程的应用场景
3、小结
二、多线程中的两个概念(并发和并行)
1、并发
2、并行
以2核4线程为例:(如果计算机中只要4条线程,那么它是不用切换的,但如果线程越来越多,那么这个红线就会在多个线程之间随机的进行切换)
3、小结
三、多线程的三种实现方式
1、继承Thread类的方式进行实现
自己定义一个类继承Thread并重写run方法
创建子类的对象,并启动线程
2、实现Runnable接口的方式进行实现
自己定义一个类实现Runnable接口,并重新里面的run方法
public class MyRun implements Runnable {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
//获取当前线程的对象
/*Thread t = Thread.currentThread();
System.out.println(t.getName()+"HelloWorld");
*/
System.out.println(Thread.currentThread().getName()+"HelloWorld");
}
}
}
package com.yaqi.a02threadcase2;
public class ThreadDemo {
public static void main(String[] args) {
/*
多线程的第二种启动方式:
* 1.自己定义一个类实现Runnable接口
* 2.重写里面的run方法
* 3.创建自己的类的对象
* 4.创建一个Thread类的对象,并开启线程
*/
//创建MyRun的对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//给线程设置名字
t1.setName("线程1");
t1.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
结果
3、利用Callable接口和Future接口方式的实现
package com.yaqi.a03threadcase3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式:
* 特点:可以获取到多线程运行的结果
*
* 1. 创建一个类MyCallable实现Callable接口
* 2. 重写call (是有返回值的,表示多线程运行的结果)
*
* 3. 创建MyCallable的对象(表示多线程要执行的任务)
* 4. 创建FutureTask的对象(作用管理多线程运行的结果)
* 5. 创建Thread类的对象,并启动(表示线程)
* */
//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程的对象
Thread t1 = new Thread(ft);
//启动线程
t1.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
package com.yaqi.a03threadcase3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1~100之间的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
4、多线程三种实现方式对比
四、常见的成员方法
1、get/setName方法 -- 线程名字
默认名字的由来:
序号自增
/*
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
*/
//1.创建线程的对象
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");
//2.开启线程
t1.start();
t2.start();
MyThread
package com.yaqi.a04threadmethod1;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "@" + i);
}
}
}
2、currentThread方法 -- 获取当前线程对象
static Thread currentThread() 获取当前线程的对象 细节: 当JVM虚拟机启动之后,会自动的启动多条线程 其中有一条线程就叫做main线程 他的作用就是去调用main方法,并执行里面的代码 在以前,我们写的所有的代码,其实都是运行在main线程当中
//哪条线程执行到这个方法,此时获取的就是哪条线程的对象
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);//main
3、sleep方法 -- 线程休眠
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒 细节: 1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间 2、方法的参数:就表示睡眠的时间,单位毫秒 1 秒= 1000毫秒 3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
System.out.println("11111111111");
Thread.sleep(5000);
System.out.println("22222222222");
4、set/getPriority方法 -- 线程优先级
没有设置,优先级则默认为5,优先级越高,抢到CPU的概率就越高
- 最小1
- 最大10
- 默认5
5、setDaemon方法 -- 守护线程
两个线程执行的代码不同:守护线程是陆续结束的,所以守护线程也叫做备胎线程
应用场景
6、yield方法 -- 礼让线程
但是只是尽可能的均匀,不是绝对的
7、join方法 -- 插入线程
插入线程:将土豆插入到main线程之前,只有当土豆线程执行完毕,才会轮到main线程
8、线程的生命周期
五、线程安全的问题
1、练习:设计一个程序模拟电影院卖票
出现了超出票范围或者重复票的情况:
2、买票引发的安全问题
相同的票出现多次
出现了超出范围的票
2.1、重复票的由来:(线程在执行代码的过程中,CPU的执行权随时有可能被抢走)
2.2、出现了超出范围的票:(和上面的原因相同)
3、安全问题的解决办法 -- 同步代码块
示例代码
结果
4、同步代码块中的两个小细节
4.1、细节1:synchronized要写在循环的里面
4.2、细节2:synchronized中的锁对象一定是唯一的
5、同步方法
示例代码
将同步代码块改成同步方法:
6、StringBuilder和StringBuffer的区别
两个类的方法都是相同的
但是StringBuffer是线程安全的,它里面所有的方法都是线程同步的
StringBulider:代码单线程的不需要考虑多线程当中数据安全的情况
StringBuffer:多线程环境下需要考虑数据安全则选择StringBuffer
7、Lock锁(手动加锁、释放锁)
7.1、Lock使用不规范造成的两个安全问题
Ⅰ、重复票以及超出范围票
我们在使用Thread类实现多线程时,创建自己的类,一定要注意锁对象需要唯一,即在相关变量前加上static关键字
Ⅱ、程序无法正常终止
这是由于当满足条件时,循环直接被终止,导致lock锁没有被释放
Ⅲ、正确代码(标准写法)
即将容易产生异常的代码块放入try…catch中
六、死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
注意事项:千万不要让两个锁嵌套起来!
七、生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
1、消费者等待
2、生产者等待
3、常见方法(wait/notify/notifyAll)
4、消费者与生产者代码实现
4.1、Cook.java
public class Cook extends Thread{
@Override
public void run() {
/*
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据是否到了末尾(到了末尾)
* 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
* */
while (true){
synchronized (com.yaqi.a13waitandnotify.Desk.lock){
if(com.yaqi.a13waitandnotify.Desk.count == 0){
break;
}else{
//判断桌子上是否有食物
if(com.yaqi.a13waitandnotify.Desk.foodFlag == 1){
//如果有,就等待
try {
com.yaqi.a13waitandnotify.Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//如果没有,就制作食物
System.out.println("厨师做了一碗面条");
//修改桌子上的食物状态
com.yaqi.a13waitandnotify.Desk.foodFlag = 1;
//叫醒等待的消费者开吃
com.yaqi.a13waitandnotify.Desk.lock.notifyAll();
}
}
}
}
}
}
4.2、Desk.java
package com.yaqi.a13waitandnotify;
public class Desk {
/*
* 作用:控制生产者和消费者的执行
*
* */
//是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
4.3、Foodie.java
public class Foodie extends Thread{
@Override
public void run() {
/*
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据是否到了末尾(到了末尾)
* 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
* */
while(true){
synchronized (com.yaqi.a13waitandnotify.Desk.lock){
if(com.yaqi.a13waitandnotify.Desk.count == 0){
break;
}else{
//先判断桌子上是否有面条
if(com.yaqi.a13waitandnotify.Desk.foodFlag == 0){
//如果没有,就等待
try {
com.yaqi.a13waitandnotify.Desk.lock.wait();//让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//把吃的总数-1
com.yaqi.a13waitandnotify.Desk.count--;
//如果有,就开吃
System.out.println("吃货在吃面条,还能再吃" + com.yaqi.a13waitandnotify.Desk.count + "碗!!!");
//吃完之后,唤醒厨师继续做
com.yaqi.a13waitandnotify.Desk.lock.notifyAll();
//修改桌子的状态
com.yaqi.a13waitandnotify.Desk.foodFlag = 0;
}
}
}
}
}
}
4.4、ThreadDemo.java
package com.yaqi.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();
}
}
5、阻塞队列方式(另一种等待唤醒机制)
5.1、阻塞队列的继承结构
5.2、阻塞队列实现等待唤醒机制
Cook.java
package com.yaqi.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断的把面条放到阻塞队列当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
put方法的源码中实现了Lock锁
Foodie.java:
take方法的底层也是有锁的
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断从阻塞队列中获取面条
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ThreadDemo.java:
package com.yaqi.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/*
*
* 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
* 细节:
* 生产者和消费者必须使用同一个阻塞队列
*
* */
//1.创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2.创建线程的对象,并把阻塞队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
//3.开启线程
c.start();
f.start();
}
}
打印语句是在锁的外面的,但是不会对数据造成影响,只是影响了控制台的打印阅读体验
7、多线程的6中状态
八、综合练习
1、多线程练习1(卖电影票)
2、多线程练习2(送礼品)
3、多线程练习3(打印奇数数字)
4、多线程练习4(抢红包)
package com.yaqi.test4case1;
import java.util.Random;
public class MyThread extends Thread{
//共享数据
//100块,分成了3个包
static double money = 100;
static int count = 3;
//最小的中奖金额
static final double MIN = 0.01;
@Override
public void run() {
//同步代码块
synchronized (MyThread.class){
if(count == 0){
//判断,共享数据是否到了末尾(已经到末尾)
System.out.println(getName() + "没有抢到红包!");
}else{
//判断,共享数据是否到了末尾(没有到末尾)
//定义一个变量,表示中奖的金额
double prize = 0;
if(count == 1){
//表示此时是最后一个红包
//就无需随机,剩余所有的钱都是中奖金额
prize = money;
}else{
//表示第一次,第二次(随机)
Random r = new Random();
//100 元 3个包
//第一个红包:99.98
//100 - (3-1) * 0.01
double bounds = money - (count - 1) * MIN;
prize = r.nextDouble(bounds);
if(prize < MIN){
prize = MIN;
}
}
//从money当中,去掉当前中奖的金额
money = money - prize;
//红包的个数-1
count--;
//本次红包的信息进行打印
System.out.println(getName() + "抢到了" + prize + "元");
}
}
}
}
测试类:
package com.yaqi.test4case1;
public class Test {
public static void main(String[] args) {
/*
微信中的抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。
其中,红包是共享数据。
5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
*/
//创建线程的对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
//给线程设置名字
t1.setName("小A");
t2.setName("小QQ");
t3.setName("小哈哈");
t4.setName("小诗诗");
t5.setName("小丹丹");
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
精确运算:(BigDecimal)
package com.yaqi.test4case2;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
public class MyThread extends Thread{
//总金额
static BigDecimal money = BigDecimal.valueOf(100.0);
//个数
static int count = 3;
//最小抽奖金额
static final BigDecimal MIN = BigDecimal.valueOf(0.01);
@Override
public void run() {
synchronized (MyThread.class){
if(count == 0){
System.out.println(getName() + "没有抢到红包!");
}else{
//中奖金额
BigDecimal prize;
if(count == 1){
prize = money;
}else{
//获取抽奖范围
double bounds = money.subtract(BigDecimal.valueOf(count-1).multiply(MIN)).doubleValue();
Random r = new Random();
//抽奖金额
prize = BigDecimal.valueOf(r.nextDouble(bounds));
}
//设置抽中红包,小数点保留两位,四舍五入
prize = prize.setScale(2,RoundingMode.HALF_UP);
//在总金额中去掉对应的钱
money = money.subtract(prize);
//红包少了一个
count--;
//输出红包信息
System.out.println(getName() + "抽中了" + prize + "元");
}
}
}
}
5、多线程练习5(抽奖箱抽奖)
package com.yaqi.test5;
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread {
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
//1.循环
//2.同步代码块
//3.判断
//4.判断
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
break;
} else {
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
System.out.println(getName() + "又产生了一个" + prize + "元大奖");
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
.....
*/
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
//创建线程
MyThread t1 = new MyThread(list);
MyThread t2 = new MyThread(list);
//设置名字
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//启动线程
t1.start();
t2.start();
}
}
6、多线程练习6(多线程统计并求最大值)
示例代码一:(在练习5的基础上进行修改)
package com.yaqi.test6case1;
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread {
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
//线程一
static ArrayList<Integer> list1 = new ArrayList<>();
//线程二
static ArrayList<Integer> list2 = new ArrayList<>();
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
if("抽奖箱1".equals(getName())){
System.out.println("抽奖箱1" + list1);
}else {
System.out.println("抽奖箱2" + list2);
}
break;
} else {
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
if("抽奖箱1".equals(getName())){
list1.add(prize);
}else {
list2.add(prize);
}
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
示例代码二:升级版--线程栈(示例一可以用,但不好)
改进后,这里只需要一个ArrayList就搞定了
package com.yaqi.test6case2;
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread {
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
ArrayList<Integer> boxList = new ArrayList<>();//1 //2
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
System.out.println(getName() + boxList);
break;
} else {
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
boxList.add(prize);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
package com.yaqi.test6case2;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽的过程中,不打印,抽完时一次性打印(随机) 在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
*/
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
//创建线程
MyThread t1 = new MyThread(list);
MyThread t2 = new MyThread(list);
//设置名字
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//启动线程
t1.start();
t2.start();
}
}
示例二内存图讲解:
每个线程都有自己独立的空间
7、多线程练习7(多线程之间的比较)
示例代码:(难点在于如何获取两个线程中的最大值★)
调用多线程的第三种方式Callable来实现(可以返回结果)
MyCallable.java:
package com.yaqi.test7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
ArrayList<Integer> list;
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> boxList = new ArrayList<>();//1 //2
while (true) {
synchronized (MyCallable.class) {
if (list.size() == 0) {
System.out.println(Thread.currentThread().getName() + boxList);
break;
} else {
//继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
boxList.add(prize);
}
}
Thread.sleep(10);
}
//把集合中的最大值返回
if(boxList.size() == 0){
return null;
}else{
return Collections.max(boxList);
}
}
}
package com.yaqi.test7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为 "抽奖箱1", "抽奖箱2"
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300
最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700
最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
核心逻辑:获取线程抽奖的最大值(看成是线程运行的结果)
以上打印效果只是数据模拟,实际代码运行的效果会有差异
*/
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
//创建多线程要运行的参数对象
MyCallable mc = new MyCallable(list);
//创建多线程运行结果的管理者对象
//线程一
FutureTask<Integer> ft1 = new FutureTask<>(mc);
//线程二
FutureTask<Integer> ft2 = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
//设置名字
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//开启线程
t1.start();
t2.start();
Integer max1 = ft1.get();
Integer max2 = ft2.get();
System.out.println(max1);
System.out.println(max2);
//在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
if(max1 == null){
System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为"+max2+"元");
}else if(max2 == null){
System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为"+max1+"元");
}else if(max1 > max2){
System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为"+max1+"元");
}else if(max1 < max2){
System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为"+max2+"元");
}else{
System.out.println("两者的最大奖项是一样的");
}
}
}
8、多线程练习8(多线程阶段大作业)
九、线程池
1、吃饭买碗的故事
1.1、问题
1.2、解决方案
买个碗柜,买了碗之后不摔,存入碗柜中
2、以前写多线程的弊端
3、线程池的核心原理
当有新的任务出现,且线程池线程不足时,会新建线程以满足需求,其中最大线程的数量可以自行设置
4、线程池的代码实现
4.1、Executors工具类
示例代码:
MyRunnable.java:
package com.yaqi.a01threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
测试类
package com.yaqi.a01threadpool1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool (int nThreads) 创建有上限的线程池
*/
//1.获取线程池对象
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3.销毁线程池
//pool1.shutdown();
}
}
4.2、线程复用示例
package com.yaqi.a02threadpool2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo1 {
public static void main(String[] args){
/*
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数一:核心线程数量 不能小于0
参数二:最大线程数 不能小于0,最大数量 >= 核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //核心线程数量,能小于0
6, //最大线程数,不能小于0,最大数量 >= 核心线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
}
}
4.3、创建一个有上限的线程池
5、自定义线程池(ThreadPoolExecutor)
5.1、任务拒绝策略
以下面示例为例,它会将任务4抛弃,将任务10加入
5.2、代码实现
package com.yaqi.a02threadpool2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo1 {
public static void main(String[] args){
/*
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数一:核心线程数量 不能小于0
参数二:最大线程数 不能小于0,最大数量 >= 核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //核心线程数量,能小于0
6, //最大线程数,不能小于0,最大数量 >= 核心线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
}
}
5.3、小结
6、最大并行数
6.1、什么是最大并行数?
6.2、向Java虚拟机返回可用处理器的数目
7、线程池多大才合适?
可以通过thread dump来计算CPU的计算时间和等待时间