本篇文章主要是用来巩固多线程的简单应用,如果你已经学习了多线程的有关知识,想要巩固,那不妨拿下面几道题来考验一下自己吧!
案例1:电影院售票(难度指数:一颗星)
题目:一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,要求:请用多线程模拟买票过程并打印剩余电影票数量
题目分析:1000张电影票为两个窗口(两个线程)的共享资源,领取的时间对应多线程中的sleep方法
package org.example.My_Thread_Practice; import java.util.concurrent.locks.ReentrantLock; public class Practice1 extends Thread { //电影票的个数 static int count=0; //创建锁对象 ReentrantLock lock=new ReentrantLock(); public static void main(String[] args) { Practice1 practice1=new Practice1(); //创建两个线程用来模拟两个窗口 Thread thread1=new Thread(practice1); Thread thread2=new Thread(practice1); thread1.setName("窗口1"); thread2.setName("窗口2"); thread1.start(); thread2.start(); } @Override public void run() { while(true){ lock.lock(); if(count<1000){ try { //每次领取的时间为3000毫秒 sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.unlock(); } count++; System.out.println("第"+count+"张电影票卖出了!"+"售票窗口为:"+Thread.currentThread().getName()); }else{ break; } } } }
输出如下所示:(部分输出结果)
案例2:送礼物(难度指数:两颗星)
题目: 有1000份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
题目分析:本题和第一题大同小异,区别在于多了一个判断,当礼品(共享资源)数剩下10份不再送出
package org.example.My_Thread_Practice; public class Practice2 extends Thread { static int count=0; static Object lock=new Object(); public static void main(String[] args) { Practice2 practice=new Practice2(); Thread thread1=new Thread(practice); thread1.setName("张三"); Thread thread2=new Thread(practice); thread2.setName("李四"); thread1.start(); thread2.start(); } @Override public void run() { while(true){ synchronized (lock) { count++; if (count < 989) { System.out.println("第" + count + "份礼物被" + Thread.currentThread().getName() + "送出!还剩" + (989 - count) + "份礼物!"); } else { break; } } } } }
输出如下所示:(部分输出结果)
案例3:找奇数(难度指数:两颗星)
题目: 同时开启两个线程,共同获取1-100之间的所有数字,要求:将输出所有的奇数
package org.example.My_Thread_Practice; public class Practice3 extends Thread { static Object lock=new Object(); public static void main(String[] args) { Practice3 practice3=new Practice3(); Thread thread1=new Thread(practice3); Thread thread2=new Thread(practice3); thread1.setName("线程1"); thread2.setName("线程2"); thread1.start(); thread2.start(); } @Override public void run() { for(int i=1;i<=100;i++){ synchronized (lock){ if(i%2==1){ System.out.println(i); } } } } }
输出如下所示:(部分输出结果)
案例4:抢红包(难度指数:三颗星)
题目:抢红包:假设100块分成三个红包,现在有5个人去抢,其中,红包是共享数据,5个人是5条线程,
打印输出结果如下:
XXX抢到了XXX元,
XXX抢到了XXX元,
XXX抢到了XXX元,
XXX没抢到,
XXX没抢到
题目分析:100元红包作为共享资源,被分成三个,供5个人去抢,既然只有5个红包,那么则说明,有两个人一定没有抢到,因此,我们首先需要确定红包的几个特殊值,必须且只有三个红包,那么就不能出现这100块钱被一个或者两个人抢了,必须三个人都有那么一点,因此红包的最大金额只能是99.98,剩下的0.02分别被两个人平分。红包的数量只有三个,那么当红包数量只剩一个时,就不需要生成随机数了,直接将当前的剩余金额全部给最后一个红包,并且为了尽可能的贴近现实,所以当红包生成的红包金额小于0.01时,我们直接将其修改为0.01
package main.java.My_Thread_Practice; public class Practice4 extends Thread{ //共享数据 static double sum=100;//红包总金额为100元 static int RedBag=3;//红包的个数为3个 static final double MIN=0.01;//最小中奖金额 public static void main(String[] args) { Practice4 practice=new Practice4(); Thread thread1=new Thread(practice); Thread thread2=new Thread(practice); Thread thread3=new Thread(practice); Thread thread4=new Thread(practice); Thread thread5=new Thread(practice); //我们可通过Ctrl+Shift+Alt快速选中多行,再预留出多行,快速可编写多行代码 //5个人同时去抢,也就是五条线程 thread1.setName("张三"); thread2.setName("李四"); thread3.setName("王五"); thread4.setName("小明"); thread5.setName("小红"); //启动线程 thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } @Override public void run() { synchronized (Practice4.class){ //红包抢完了没? //抢完了,直接输出没红包了 //没抢完,再进行随机数抢红包 if(RedBag==0){ System.out.println(Thread.currentThread().getName()+"没有抢到红包!"); }else{ double prize;//红包的金额 if(RedBag==1){//如果剩余红包数为1个,那么直接将剩下的所有金额给最后一个红包 prize=sum; }else {//通过随机数去生成不同的红包金额 //Math.random()生成一个0-1之间的随机小数 double random = Math.random(); //确定随机数的范围---并将最终的金额保留小数点后两位 prize= Double.parseDouble(String.format("%.2f",random * (sum-(RedBag-1)*MIN))); if(prize<MIN){//如果最终获取到的结果小于0.01,那么直接让其为最小金额0.01 prize= MIN; } }//更新余额 sum=sum-prize; RedBag--; System.out.println(Thread.currentThread().getName()+"抢到了"+prize+"元!"); } } } }
输出如下所示:
案例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 元大奖
题目分析:我们必须保证奖池的唯一性,因此不能最好的方法就是奖池中的某个奖被抽走后,我们立即删除它,最开始我是写了一个方法通过Random获取奖池中的任意值,表示随机数,然后再将其删除,但是ArrayList是不能被修改的,而且那种方法也很麻烦,下面的这种方法更加简单
package main.java.My_Thread_Practice; import java.util.ArrayList; import java.util.Collections; public class Practice5 extends Thread { ArrayList<Integer> arrayList; public Practice5(ArrayList<Integer> arrayList){ this.arrayList=arrayList; } @Override public void run() { while (true){ synchronized (Practice5.class){ if(arrayList.size()==0){ break; }else{ //将集合中的元素随机排列 Collections.shuffle(arrayList); //我们抽中的始终为集合中的第一个元素 int prize=arrayList.remove(0); System.out.println(Thread.currentThread().getName()+"又产生了一个"+prize+"元大奖"); } } } } public static void main(String[] args) { //将list通过有参构造传递,而不是定义在run方法中,这样能够确保我们无论创建多少条线程,始终操作的都是同一个list ArrayList<Integer> list=new ArrayList<>(); Collections.addAll(list,10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700); Practice5 practice = new Practice5(list); Thread thread1 = new Thread(practice); Thread thread2 = new Thread(practice); thread1.setName("抽奖箱1"); thread2.setName("抽奖箱2"); thread1.start(); thread2.start(); } }
输出如下所示:
声明一点,由于我们的测试数据很少,因此想要达到我上述这种有抽奖箱1,也有抽奖箱2的效果,方法1:可以多运行几次,方法2,在同步代码块的外面增加sleep方法,注意不能写在同步方法块里面,原因是,如果你写在里面,那么即使线程休眠一段时间,其他线程也不能执行同步代码块呀,所以并没达到我们想要的让两个线程尽可能的均匀执行的效果
案例5:抢红包(进阶)(难度指数:四颗半星)
题目:在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)
在此次抽奖过程中,抽奖箱1总共产生了6个奖项。分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
题目分析:它要求最后一次性进行打印,那么就说明,我们需要根据线程分别将奖项存放,并且需要计算最大值和总和,这里我们可以使用Collections提供的max方法求出最大值,也可以使用和我一样的,将集合定义为TreeSet而不是ArrayList,我这样做的好处就是TreeSet它默认是根据元素的自然顺序排序的,它本身就是一个有序集合,因此我寻找最大值只需要调用last方法就可以求出,总金额的话,就需要我们去变量集合累计求和
package main.java.My_Thread_Practice; import java.util.ArrayList; import java.util.Collections; import java.util.TreeSet; public class Practice5 extends Thread { ArrayList<Integer> arrayList; //存放抽奖箱1产生的奖 TreeSet<Integer> bag1; //存放抽奖箱2产生的奖 TreeSet<Integer> bag2; //抽奖箱1的总金额 int sum1=0; //抽奖箱2的总金额 int sum2=0; public Practice5(ArrayList<Integer> arrayList,TreeSet<Integer> bag1,TreeSet<Integer> bag2){ this.bag1=bag1; this.bag2=bag2; this.arrayList=arrayList; } @Override public void run() { while (true){ synchronized (Practice5.class){ if(arrayList.size()==0){//当奖池中的奖分配完毕后,我们直接将两个抽奖箱中的结果输出即可 if(Thread.currentThread().getName().equals("抽奖箱1")){ for (Integer i:bag1) { sum1+=i; } System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()+"总共产生了"+bag1.size()+"个奖项,分别为"+bag1+"最高奖项为:"+bag1.last()+"元,总计额为"+sum1+"元"); }else { if (Thread.currentThread().getName().equals("抽奖箱2")) { for (Integer i : bag2) { sum2 += i; } System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName() + "总共产生了" + bag2.size() + "个奖项,分别为" + bag2 + "最高奖项为:" + bag2.last() + "元,总计额为" + sum2 + "元"); } } break; }else{ //将集合中的元素随机排列 Collections.shuffle(arrayList); //我们抽中的始终为集合中的第一个元素 int prize=arrayList.remove(0); if(Thread.currentThread().getName().equals("抽奖箱1")){ bag1.add(prize); }else { bag2.add(prize); } } } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public static void main(String[] args) { //将list通过有参构造传递,而不是定义在run方法中,这样能够确保我们无论创建多少条线程,始终操作的都是同一个list ArrayList<Integer> list=new ArrayList<>(); //抽奖箱和奖池一样都是使用有参构造的形式传递,为的就是确保我们始终操作的抽奖箱是同一个 //由于我们最终还需要分别获得两个箱子中的最高奖项,因此我们可以直接使用TreeSet,将元素添加到集合中就能够直接排序的功能 //通过last方法就可以获取TreeSet中的最大值 // 抽奖箱1 TreeSet<Integer> bag1 = new TreeSet<>(); //抽奖箱2 TreeSet<Integer> bag2 = new TreeSet<>(); Collections.addAll(list,10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700); Practice5 practice = new Practice5(list,bag1,bag2); Thread thread1 = new Thread(practice); Thread thread2 = new Thread(practice); thread1.setName("抽奖箱1"); thread2.setName("抽奖箱2"); thread1.start(); thread2.start(); } }
输出如下所示:
案例5:抢红包(进阶)优化版—线程栈
上述只是要求两个抽奖箱,因此我们创建了两个TreeSet,那么要是有100个,甚至1000个抽奖箱,我们难道要创建这么多的抽奖箱吗?当然不是,因此下述的这种方法,更加灵活和简答的实现了上述功能
Java中的堆是唯一的,而栈并不是唯一的
package main.java.My_Thread_Practice; import java.util.ArrayList; import java.util.Collections; import java.util.TreeSet; public class Practice5 extends Thread { ArrayList<Integer> arrayList; public Practice5(ArrayList<Integer> arrayList){ this.arrayList=arrayList; } @Override public void run() { //无论哪条线程抢占到CPU执行run方法都会创建它专属的抽奖箱 TreeSet<Integer> boxList=new TreeSet<>(); //每个抽奖箱都会创建它专属的总金额 int sum=0; while (true){ synchronized (Practice5.class) { if (arrayList.size() == 0){//当奖池中的奖分配完毕后,我们直接将两个抽奖箱中的结果输出即可 for (Integer i:boxList) { sum+=i; } System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName() + "总共产生了" + boxList.size() + "个奖项,分别为" + boxList + "最高奖项为:" + boxList.last() + "元,总计额为" + sum + "元"); break; }else{ Collections.shuffle(arrayList); int prize=arrayList.remove(0); boxList.add(prize); } } } try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static void main(String[] args) { //将list通过有参构造传递,而不是定义在run方法中,这样能够确保我们无论创建多少条线程,始终操作的都是同一个list ArrayList<Integer> list=new ArrayList<>(); Collections.addAll(list,10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700); Practice5 practice = new Practice5(list); Thread thread1 = new Thread(practice); Thread thread2 = new Thread(practice); thread1.setName("抽奖箱1"); thread2.setName("抽奖箱2"); thread1.start(); thread2.start(); } }
输出如下所示:
案例5:抢红包(进阶)优化版—线程之间的比较(难度指数:五颗星)
题目:在次基础上,我们再一次升级难度,要求打印出
在此抽奖过程中,抽奖箱X中产生了最大奖项,该奖金额为X元
这里设计到了线程间的比较,由于我们是将12个奖项随机的分配到两个奖箱中,因此在前面我们求出了每个箱子中的最大奖项还不够,我们这次还要想办法比较两个奖箱中最大值中较大的一个,并将其输出
题目分析:此次需求在之前的基础上,增加了让我们求两个奖项中较大的一个,这就涉及到了线程间的比较,我们可以通过线程的实现方式中的第三种,它的call方法能够返回线程的比较结果,我们只需要在main方法中分别获取不同线程的返回结果,再输出最大的即可,也可以通过将各个线程中的最大值以及线程名称加入到hashMap中,最后遍历hashMap求出最大值
第一种方法:使用Callable接口
package main.java.My_Thread_Practice; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class MyCallable implements Callable<Integer> { 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 myCallable=new MyCallable(list); FutureTask<Integer> ft1=new FutureTask<>(myCallable); FutureTask<Integer> ft2=new FutureTask<>(myCallable); Thread thread1=new Thread(ft1); Thread thread2=new Thread(ft2); thread1.setName("抽奖箱1"); thread2.setName("抽奖箱2"); thread1.start(); thread2.start(); //表示线程1获取到的最大值--抽奖箱1获得的最大值 Integer max1=ft1.get(); //表示线程2获取到的最大值--抽奖箱2获得的最大值 Integer max2=ft2.get(); //输出两个抽奖箱最大值中较大的那个 if(max1>max2){ System.out.println("在此抽奖过程中,抽奖箱1中产生了最大奖项,该奖金额为"+max1); }else { System.out.println("在此抽奖过程中,抽奖箱2中产生了最大奖项,该奖金额为"+max2); } } public MyCallable (ArrayList<Integer> integers){ this.arrayList=integers; } ArrayList<Integer> arrayList; @Override public Integer call() throws Exception { //每条线程都会创建它专属的抽奖箱 TreeSet<Integer> boxList = new TreeSet<>(); //每个抽奖箱都会创建它专属的总金额 int sum = 0; while (true) { synchronized (Practice5.class) { if (arrayList.size() == 0) {//当奖池中的奖分配完毕后,我们直接将两个抽奖箱中的结果输出即可 for (Integer i : boxList) { sum += i; } System.out.println("在此次抽奖过程中," + Thread.currentThread().getName() + "总共产生了" + boxList.size() + "个奖项,分别为" + boxList + "最高奖项为:" + boxList.last() + "元,总计额为" + sum + "元"); break; } else { Collections.shuffle(arrayList); int prize = arrayList.remove(0); boxList.add(prize); } } Thread.sleep(100); } //将每个抽奖箱中的最大值返回 return Collections.max(boxList); } }
输出如下所示:
第二种方式:使用hashMap
将每个奖项中产生的最大值和对应的奖箱名字存放在hashmap中,最后通过遍历hashmap输出最大值,即为所有奖箱中的最大值
package main.java.My_Thread_Practice; import java.util.*; public class Practice6 extends Thread { ArrayList<Integer> arrayList; HashMap<String,Integer> hashMap=new HashMap<>(); public Practice6(ArrayList<Integer> arrayList){ this.arrayList=arrayList; } @Override public void run() { //无论哪条线程抢占到CPU执行run方法都会创建它专属的抽奖箱 TreeSet<Integer> boxList=new TreeSet<>(); //每个抽奖箱都会创建它专属的总金额 int sum=0; while (true){ synchronized (Practice5.class) { if (arrayList.size() == 0){//当奖池中的奖分配完毕后,我们直接将两个抽奖箱中的结果输出即可 for (Integer i:boxList) { sum+=i; } System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName() + "总共产生了" + boxList.size() + "个奖项,分别为" + boxList + "最高奖项为:" + boxList.last() + "元,总计额为" + sum + "元"); hashMap.put(Thread.currentThread().getName(),boxList.last()); break; }else{ Collections.shuffle(arrayList); int prize=arrayList.remove(0); boxList.add(prize); } } } Map.Entry<String, Integer> maxEntry = null; for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { if (maxEntry == null || entry.getValue().compareTo(maxEntry.getValue()) > 0) { maxEntry = entry; } } System.out.println("在此次抽奖过程中," + maxEntry.getKey() + "中产生了最大奖项,该奖项金额为:" + maxEntry.getValue()+"元"); try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static void main(String[] args) { //将list通过有参构造传递,而不是定义在run方法中,这样能够确保我们无论创建多少条线程,始终操作的都是同一个list ArrayList<Integer> list=new ArrayList<>(); HashMap<String ,Integer> hashMap=new HashMap<>(); Collections.addAll(list,10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700); Practice6 practice = new Practice6(list); Thread thread1 = new Thread(practice); Thread thread2 = new Thread(practice); thread1.setName("抽奖箱1"); thread2.setName("抽奖箱2"); thread1.start(); thread2.start(); } }
输出如下所示:
这种方式有个弊端就是最大奖项哪条输出语句输出的次数为线程的个数