菜鸟之路Day20一一多线程(二)
作者:blue
时间:2025.2.27
0.概述
内容学习自黑马程序员BV1yW4y1Y7Ms
1.多线程练习1
一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,
要求:用多线程模拟买票的过程并打印剩余电影票的数量
Thread中的Run方法:
public class MyThread extends Thread{
//电影票的总数
static int tickets = 1000;
//锁对象
static final Object lock = new Object();
@Override
public void run() {
while(true){
synchronized (lock){
if(tickets==0){
break;
}
else{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
tickets--;
System.out.println(Thread.currentThread().getName()+"分发了一张电影票剩余"+tickets+"张");
}
}
}
}
}
测试类:
public class MyThreadTestDemo1 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}
2.多线程练习2
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。
利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来,
Thread中的Run方法
public class MyThreadTwo extends Thread{
//礼物
static int gift = 100;
//锁对象
static final Object lock = new Object();
@Override
public void run() {
while(true){
synchronized (lock){
if(gift<10) {
break;
}
else {
gift--;
System.out.println(Thread.currentThread().getName()+"送出一份礼物,还剩下"+gift+"份礼物");
}
}
}
}
}
测试类:
public class MyThreadTestDemo2 {
public static void main(String[] args) {
MyThreadTwo t1 = new MyThreadTwo();
MyThreadTwo t2 = new MyThreadTwo();
t1.setName("小L");
t2.setName("小N");
t1.start();
t2.start();
}
}
3.多线程练习3
同时开启两个线程,共同获取1-100之间的所有数字
要求:将输出所有的奇数。
Thread中的Run方法
public class MyThreadThree extends Thread{
static int num = 1;
static final Object lock = new Object();
@Override
public void run() {
while(true){
synchronized (lock) {
if(num>100) {
break;
}
else {
if(num%2==1){
System.out.println(num);
}
num++;
}
}
}
}
}
测试类
public class MyThreadTestDemo3 {
public static void main(String[] args) {
MyThreadThree t1 = new MyThreadThree();
MyThreadThree t2 = new MyThreadThree();
t1.start();
t2.start();
}
}
4.多线程练习4
抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。
其中,红包是共享数据。
5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
public class MyThreadFour extends Thread {
//共享数据
//红包总金额
static double money = 100;
//红包总数
static int count = 3;
//最小金额
static final double MIN = 0.01;
//锁对象
static final Object LOCK = new Object();
@Override
public void run() {
synchronized (LOCK) {
if (count == 0) {
System.out.println(Thread.currentThread().getName() + "没抢到");
} else {
if (count == 1) {
//最后一个人,红包金额不用随机
System.out.println(Thread.currentThread().getName() + "抢到了" + money + "元");
} else {
//红包金额需要随机
//随机的范围
//以第一人举例:最极端的范围是:99.8 0.01 0.01
//99.8 = 100-(count-1)*MIN
double bounds = money - (count - 1) * MIN;
Random rd = new Random();
//随机到的金额
double value = rd.nextDouble(bounds) + MIN;
System.out.println(Thread.currentThread().getName() + "抢到了" + value + "元");
money = money - value;
}
count--;
}
}
}
}
public class MyThreadTestDemo4 {
public static void main(String[] args) {
MyThreadFour t1 = new MyThreadFour();
MyThreadFour t2 = new MyThreadFour();
MyThreadFour t3 = new MyThreadFour();
MyThreadFour t4 = new MyThreadFour();
MyThreadFour t5 = new MyThreadFour();
t1.setName("a");
t2.setName("b");
t3.setName("c");
t4.setName("d");
t5.setName("e");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
5.多线程练习5
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为
{10,5,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
Thread中的Run方法
public class MyThreadFive extends Thread{
//共享数据
ArrayList<Integer> list;
static final Object LOCK = new Object();
public MyThreadFive(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
while(true){
synchronized (LOCK){
if (list.isEmpty()) {
break;
}
else {
Collections.shuffle(list);
int value = list.removeFirst();
System.out.println(Thread.currentThread().getName()+"又产生一个"+value+"大奖");
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试类
public class MyThreadTestDemo5 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
MyThreadFive t1 = new MyThreadFive(list);
MyThreadFive t2 = new MyThreadFive(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
6.多线程练习6
在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)
在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
Thread中的Run方法
public class MyThreadSix extends Thread{
//共享数据
ArrayList<Integer> list;
static final Object LOCK = new Object();
ArrayList<Integer> list1 = new ArrayList<>(); //抽奖箱1
ArrayList<Integer> list2 = new ArrayList<>(); //抽奖箱2
public MyThreadSix(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
while(true){
synchronized (LOCK){
if (list.isEmpty()) {
if("抽奖箱1".equals(getName())){
int sum = 0;
System.out.println("抽奖箱1总共产生了"+list1.size()+"个奖项");
System.out.print("分别为");
System.out.print(list1);
System.out.println("最高为"+Collections.max(list1));
for (Integer i : list1) {
sum+=i;
}
System.out.println("总计为"+sum);
}
else if("抽奖箱2".equals(getName())){
int sum = 0;
System.out.println("抽奖箱2总共产生了"+list2.size()+"个奖项");
System.out.print("分别为");
System.out.print(list2);
System.out.println("最高为"+Collections.max(list2));
for (Integer i : list2) {
sum+=i;
}
System.out.println("总计为"+sum);
}
break;
}
else {
Collections.shuffle(list);
int value = list.removeFirst();
if("抽奖箱1".equals(getName())){
list1.add(value);
}
else if("抽奖箱2".equals(getName())){
list2.add(value);
}
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试类
public class MyThreadTestDemo6 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
MyThreadSix t1 = new MyThreadSix(list);
MyThreadSix t2 = new MyThreadSix(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
以上的代码并不利于维护,我们可以利用线程栈的思想来解决这个问题,每个线程在执行时都会得到一个listBox的局部变量存放在栈中
public class MyThreadSix extends Thread {
//共享数据
ArrayList<Integer> list;
static final Object LOCK = new Object();
public MyThreadSix(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
ArrayList<Integer> listBox = new ArrayList<>();
while (true) {
synchronized (LOCK) {
if (list.isEmpty()) {
int sum = 0;
System.out.println(getName() + "总共产生了" + listBox.size() + "个奖项");
System.out.print("分别为");
System.out.print(listBox);
System.out.println("最高为" + Collections.max(listBox));
for (Integer i : listBox) {
sum += i;
}
System.out.println("总计为" + sum);
break;
} else {
Collections.shuffle(list);
int value = list.removeFirst();
listBox.add(value);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
7.多线程练习7
在上一题基础上继续完成如下需求:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300
最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700
最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
以上打印效果只是数据模拟,实际代码运行的效果会有差异
思路:本题与前几题不同的地方在于,需要比较不同线程的执行结果,我们可以实现一个 Callable接口,来获取线程执行的返回值
实现Callable接口
public class MyCallable implements Callable<Integer> {
ArrayList<Integer> list;
static final Object LOCK = new Object();
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> listBox = new ArrayList<>();
while (true) {
synchronized (LOCK) {
if (list.isEmpty()) {
int sum = 0;
System.out.println(Thread.currentThread().getName() + "总共产生了" + listBox.size() + "个奖项");
System.out.print("分别为");
System.out.print(listBox);
System.out.println("最高为" + Collections.max(listBox));
for (Integer i : listBox) {
sum += i;
}
System.out.println("总计为" + sum);
break;
} else {
Collections.shuffle(list);
int value = list.removeFirst();
listBox.add(value);
}
}
Thread.sleep(10);
}
return Collections.max(listBox); //返回最大值
}
}
测试类
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
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();
System.out.println("其中最高奖项为"+Math.max(ft1.get(), ft2.get()));
}
}
8.线程池
之前写多线程的弊端
弊端1:用到线程的时候就创建
弊端2:用完后线程就消失
线程池主要核心原理
①创建一个池子,池子中是空的
②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接 复用已有的线程即可
③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
线程池的代码实现
1.创建线程池
2.提交任务
3.所有的任务全部执行完毕,关闭线程池
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
public static ExecutorService newCachedThreadPool() //创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads) //创建有上限的线程池
创建一个上限为3的线程池,其线程数最大为3
public class ThreadPoolDemo1 {
public static void main(String[] args) {
ExecutorService pool1 = Executors.newFixedThreadPool(3);
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
自定义线程池
核心线程数量(不能小于0)
线程池中最大线程的数量(最大数量>=核心线程数量)
空闲时间(值)(不能小于0)
空闲时间(单位)(用TimeUnit指定)
阻塞队列(不能为null)
创建线程的方式(不能为null)
要执行的任务过多时的解决方案(不能为null)
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
//(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
3,//核心线程数量,不能小于0
6,//最大线程数,不能小于0,最大数量 >= 核心线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
}
}
计算电脑CPU的最大并行数
最大并行数:cpu最多同时运行的线程数
public class ThreadPoolDemo3 {
public static void main(String[] args) {
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
}
}
线程池多大合适?
CPU 密集型运算: 最大并行数+1
I/O 密集型运算 : 最大并行数 期望CPU利用率 总时间(CPU计算时间 + 等待时间) / CPU计算时间