多线程4:线程池、并发、并行、综合案例-抢红包游戏
欢迎来到“雪碧聊技术”CSDN博客!
在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将不断探索Java的深邃世界,分享最新的技术动态、实战经验以及项目心得。
让我们一同在Java的广阔天地中遨游,携手提升技术能力,共创美好未来!感谢您的关注与支持,期待在“雪碧聊技术”与您共同成长!
目录
方式一:JDK5.0开始,提供了代表线程池的接口:ExecutorService。使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
一、认识线程池
1、什么是线程池?
线程池就是一个可以复用线程的技术。
2、不复用线程的问题
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理,创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
就好比:每次吃完饭都扔一个碗,长期以来肯定吃不消这么大的消耗。
3、线程池的工作原理
①工作线程
工作线程:就是线程池里面的线程,来处理任务队列里面的任务,有点类似于饭店的服务员。
②任务队列
任务队列:里面存放的都是实现了Runnable/Callable接口的任务类,等待工作线程的完成。
二、创建线程池
1、如何创建线程池?
方式一:JDK5.0开始,提供了代表线程池的接口:ExecutorService。使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
该实现类 ThreadPoolExecutor的构造器一共有七个参数,如下:
举例:
public class Test9 {
public static void main(String[] args) {
//目标:创建线程池对象来使用
//1、使用线程池(ExecutorService接口)的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
}
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
下面五会讲。
2、线程池的注意事项
①什么时候开始创建临时线程?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
②什么时候会拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
3、任务拒绝策略
三、处理Runnable任务
1、ExecutorService的常用方法
举例:
public class Test9 {
public static void main(String[] args) {
//目标:创建线程池对象来使用
//1、使用线程池(ExecutorService接口)的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//2、使用线程池处理任务!看会不会复用线程?
Runnable target = new My_Runnable();//任务对象
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//3、关闭线程池:一般不关闭线程池
//pool.shutdown();//等所有任务执行完毕后,再关闭线程池!
//pool.shutdownNow();//立即关闭,不管任务是否执行完毕!
}
}
//线程任务类(等待被线程对象执行的任务)
class My_Runnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了~~");
}
}
执行结果:
pool-1-thread-2执行了~~
pool-1-thread-3执行了~~
pool-1-thread-2执行了~~
pool-1-thread-3执行了~~
pool-1-thread-2执行了~~
pool-1-thread-1执行了~~
可见此时6个任务,但是线程池就3个核心线程,因此会产生复用。于是就看到上面确实有很多线程执行了好几个任务,因此线程确实是被复用了。
2、总结
四、处理Callable任务
1、ExecutorService的常用方法
举例:
public class Test10 {
public static void main(String[] args) {
//目标:创建线程池对象来使用
//1、使用线程池(ExecutorService接口)的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//2、使用线程池处理任务!看会不会复用线程?
Future<String> f1 = pool.submit(new My_Callable(100));
Future<String> f2 = pool.submit(new My_Callable(200));
Future<String> f3 = pool.submit(new My_Callable(300));
Future<String> f4 = pool.submit(new My_Callable(400));
//输出线程执行完,返回的结果
try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
//3、关闭线程池:一般不关闭线程池
//pool.shutdown();//等所有任务执行完毕后,再关闭线程池!
//pool.shutdownNow();//立即关闭,不管任务是否执行完毕!
}
}
//1、定义一个实现Callable接口的实现类
class My_Callable implements Callable<String>{
private int n;
public My_Callable(int n){
this.n = n;
}
//2、重写call方法,定义线程执行体
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum+=i;
}
return Thread.currentThread().getName()+"-"+n+"的和是:"+sum;
}
}
运行结果:
pool-1-thread-1-100的和是:5050
pool-1-thread-2-200的和是:20100
pool-1-thread-3-300的和是:45150
pool-1-thread-1-400的和是:80200
可见此时线程1被复用。
2、总结
五、通过Executors工具类,创建线程池
1、该工具类中有哪些静态方法?
举例:
public class Test10 {
public static void main(String[] args) {
//1、通过线程池工具类:Executors,调用其静态方法直接得到线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
//2、使用线程池处理任务!看会不会复用线程?
Future<String> f1 = pool.submit(new My_Callable(100));
Future<String> f2 = pool.submit(new My_Callable(200));
Future<String> f3 = pool.submit(new My_Callable(300));
Future<String> f4 = pool.submit(new My_Callable(400));
try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
//3、关闭线程池:一般不关闭线程池
//pool.shutdown();//等所有任务执行完毕后,再关闭线程池!
//pool.shutdownNow();//立即关闭,不管任务是否执行完毕!
}
}
//1、定义一个实现Callable接口的实现类
class My_Callable implements Callable<String>{
private int n;
public My_Callable(int n){
this.n = n;
}
//2、重写call方法,定义线程执行体
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum+=i;
}
return Thread.currentThread().getName()+"-"+n+"的和是:"+sum;
}
}
运行结果:
pool-1-thread-1-100的和是:5050
pool-1-thread-2-200的和是:20100
pool-1-thread-3-300的和是:45150
pool-1-thread-3-400的和是:80200
2、Executors工具类使用时可能存在的风险
3、总结
六、并发、并行
1、什么是进程?
正在运行的程序(软件)就是一个进程。
举例
2、什么是线程?
线程属于进程,一个进程可以同时运行很多个线程。
进程中的多个线程是并发+并行执行的。
3、什么是并发?
并发:进程中的线程是由CPU负责调度执行的,但CPU同时处理线程的数量是有限的(由CPU的核数决定),为了能保证全部线程都能往前执行,因此CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉是这些线程在同时执行,这就是并发。
举例
假设我的CPU是单核的(同一时刻只能运行一个线程),但是会让很多线程轮流切换上CPU执行,切换速度极快,仿佛是同时在运行所有进程中的线程,这就叫“并发”。
4、什么是并行?
并行:在同一个时刻上,同时有多个线程(数量取决于CPU的核数)在被CPU调度执行。
举例
假设我的CPU是4核,那么同一时刻,会有4个线程同时上cpu执行,这就叫“并行”。
说白了,并行的线程数,取决于CPU的核数。
5、总结
举例
通过查看某人电脑的任务管理器,得知该电脑的CPU是20核的,也就是说,同一时刻允许20个线程上CPU执行,那么这20个线程就是并行关系;
该电脑目前需要处理6800个线程,那么就会20个一组上CPU轮流切换运行,由于切换速度极快,仿佛是同时在运行,这就叫“并发”。
六、综合案例-抢红包游戏
1、介绍
2、编码
public class Test12 {
public static void main(String[] args) {
/**
* 目标:完成多线程的综合小案例
* 红包雨游戏:某企业有100名员工,员工的工号依次是1,2,3,4...到100
* 现在公司举办了年会活动,活动中有一个红包雨环节,要求共计发出200个红包雨,其中小红包在【1 - 30】 元之间
* 总占比80%,大红包在【31 - 100】元, 总占比20%
* 分析:100个员工实际上就是100个线程,来竞争200个红包
*/
List<Integer> redPacket = getRedPacket();
//2、定义线程类,创建100个线程,竞争同一个集合
for (int i = 1; i <= 100; i++) {
new EmployeeGetPacket(redPacket, "员工"+i).start();
}
}
//1、准备这200个随机的红包返回,放到这个List集合中
public static List<Integer> getRedPacket(){
Random r = new Random();
//其中小红包在【1 - 30】 元之间,总占比80%(即160个),大红包在【31 - 100】元,总占比为20%(即40个)
ArrayList<Integer> redPacket = new ArrayList<>();
for (int i = 1; i <= 160; i++) {
redPacket.add(r.nextInt(30) + 1);
}
for (int i = 1; i <= 40; i++) {
redPacket.add(r.nextInt(70) + 31);
}
return redPacket;
}
}
//员工抢红包的线程
class EmployeeGetPacket extends Thread{
private List<Integer> redPacket;//红包集合
//构造器
public EmployeeGetPacket(List<Integer> redPacket, String name) {
super(name);
this.redPacket = redPacket;
}
public void run() {
String name = Thread.currentThread().getName();
while(true){
//100个人来抢redPackage集合中的钱
synchronized (redPacket){
if(redPacket.size() == 0){
break;
}
//随机一个索引得到红包
int index = (int)(Math.random() * redPacket.size());
Integer money = redPacket.remove(index);
System.out.println(name + "抢到了" + money + "元");
if(redPacket.size() == 0){
System.out.println("活动结束!");
break;
}
}
try {
Thread.sleep(1000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
}
}
}
运行结果:
员工1抢到了3元
员工99抢到了20元
员工98抢到了59元
员工100抢到了14元
员工96抢到了2元
员工97抢到了17元
员工95抢到了20元
员工94抢到了12元
员工93抢到了8元
员工92抢到了26元
员工91抢到了7元
...不全部展示了,因为200条抢红包记录,太长了...
以上就是线程池、并发、并行、抢红包游戏案例的全部内容,想了解更多Java知识,请关注本博主~~
原文地址:https://blog.csdn.net/qq_63981644/article/details/143848262
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!