目录
2.1、重复票的由来:(线程在执行代码的过程中,CPU的执行权随时有可能被抢走)
4.1、细节1:synchronized要写在循环的里面编辑
4.2、细节2:synchronized中的锁对象一定是唯一的
6、StringBuilder和StringBuffer的区别
十、多线程的额外扩展内容准备面试时可以再突击学习,资料可见《多线程(额外扩展).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的计算时间和等待时间