有了多线程,我们就可以让程序同时做多件事情
作用:
提高效率
应用场景:
只要想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器...
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式:
①继承Thread类的方式进行实现
实现步骤:
1.自己定义一个类继承Thread
2.重写run方法
3.创建子类对象,并启动线程
代码演示:
MyThread类(继承Thread):
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "aaa");}}
}
测试类ThreadDemo1:
public class ThreadDemo1 {public static void main(String[] args) {/*1.创建类继承Thread2.重写run方法3.创建子类对象,启动线程*///创建子类对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();//给线程起名字便于观察结果t1.setName("线程1");t2.setName("线程2");//启动线程t1.start();t2.start();}
}
运行结果:
(只截取了一部分)两个进程并发执行
②实现Runnable接口的方式进行实现
实现步骤:
1.定义一个类implements Runnable接口
2.重写run方法
3.创建这个类的对象
4.创建线程对象,启动线程
代码演示:
MyRun类(实现Runnable):
public class MyRun implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "aaa");}}
}
测试类ThreadDemo2:
public class ThreadDemo2 {public static void main(String[] args) {/*1.定义一个类implements Runnable接口2.重写run方法3.创建这个类的对象4.创建线程对象,启动线程*///创建这个类的对象MyRun mr = new MyRun();//创建线程对象Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);//起名字t1.setName("线程1");t2.setName("线程2");//启动线程t1.start();t2.start();}
}
运行结果:
(只截取了一部分)两个进程并发执行
★③利用Callable接口和Future接口方式实现
代码演示:
MyCallable类(实现Callable):
public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {sum = sum + i;}return sum;}
}
测试类ThreadDemo3:
public class ThreadDemo3 {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类的对象Thread t1 = new Thread(ft);//启动线程t1.start();//获取结果Integer result = ft.get();System.out.println(result);}
}
运行结果:
多线程三种实现方式对比
常见成员方法:
其中前四种方法比较简单,在此简单介绍几点
1.线程的默认名字是Thread-序号,序号从0开始,随着进程创建按顺序逐个+1
2.第三个第四个方法都是哪个线程实现这两个成员方法所在的方法,则是对这个线程操作
3.让线程休眠后续代码也会运行
优先级:
线程的优先级从1~10分为10挡,1为优先级最低,10为最高。
优先级越高,在线程并发中抢到CPU的概率就更高(但不是绝对的)。
线程的默认优先级都为5。
代码演示:
MyThread类:
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + ":" + i);}}
}
测试类ThreadDemo4:
public class ThreadDemo4 {public static void main(String[] args) {//创建进程MyThread t1 = new MyThread();MyThread t2 = new MyThread();//设置优先级t1.setPriority(10);t2.setPriority(1);//设置名字t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}
运行结果:
守护线程:
特点:
当其他非守护线程执行完毕之后,守护线程也会陆续结束,不管是否执行完代码。
代码演示:
MyThread1:
public class MyThread1 extends Thread {@Overridepublic void run() {for (int i = 0; i <= 100; i++) {System.out.println(getName() + ":" + i);}}
}
MyThread2:
public class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 0; i <= 10; i++) {System.out.println(getName() + ":" + i);}}
}
测试类ThreadDemo5:
public class ThreadDemo5 {public static void main(String[] args) {//创建进程MyThread1 t1 = new MyThread1();MyThread2 t2 = new MyThread2();//设置名字t1.setName("沸羊羊");t2.setName("美羊羊");t1.setDaemon(true);t1.start();t2.start();}
}
运行结果:
出让线程:
执行Thread.yield命令后就是把CPU的执行权交出,然后各个线程重新抢夺。
插队线程:
执行方法 ‘线程对象.join()’ 后 表示把这个线程,插入到当前线程之前,先执行完这个线程,再执行当前方法的线程。
线程的生命周期
线程的生命周期是:新建状态,就绪状态,运行状态,阻塞状态,死亡状态
虚拟机中线程的六种状态
新建状态、就绪状态、阻塞状态、等待状态、计时等待、结束状态 (没有运行状态)
多线程代码编写核心逻辑
1.循环
2.同步代码块
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据是否到了末尾(没到末尾,执行核心逻辑)
同步代码块:
格式:
synchronized (锁对象) {
同步代码块
}
特点:
当一个线程抢到CPU的执行权进入到同步代码块中执行代码了,那么其他的线程是不能再进来同步代码块的,只有当成功进入的线程执行完毕出去之后,它的锁才打开,其他的线程才能进来,而且也只能进去一个线程。
并且,如果锁对象不唯一,那么相当于有好几把锁,就可能出现不同的线程看的是不同的锁,还是会同时进去执行代码。一般我们用当前类的字节码文件作为锁对象(类名.class).
先结合一个小练习进行代码演示
练习:
某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。
代码演示:
MyThread3类:
public class MyThread3 extends Thread {//表示这个类所有的对象,都共享ticket数据static int ticket = 0;//锁对象,必须是唯一的@Overridepublic void run() {while (true) {//同步代码块synchronized (MyThread3.class) {if(ticket < 1000) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");} else {break;}}}}
}
测试类ThreadDemo6:
public class ThreadDemo6 {public static void main(String[] args) {//某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。//创建三个线程对象MyThread3 t1 = new MyThread3();MyThread3 t2 = new MyThread3();MyThread3 t3 = new MyThread3();//设置名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//启动线程t1.start();t2.start();t3.start();}
}
运行结果:
同步方法:
同步方法就是把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数) {...}
特点:
1.同步方法是锁住方法里面所有的代码
2.锁对象不能自己指定(非静态方法中是this,静态方法中是当前类的字节码文件对象)
继续结合上述小练习进行代码演示
练习:
某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。
代码演示:
MyRunnable类:
public class MyRunnable implements Runnable {int ticket = 0;@Overridepublic void run() {while(true) {if (method()) break;}}//同步方法private synchronized boolean method() {if(ticket == 1000) {return true;} else {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");}return false;}
}
测试类ThreadDemo7:
public class ThreadDemo7 {public static void main(String[] args) {//某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
运行结果:
lock锁:
继续结合上述小练习进行代码演示
练习:
某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。
代码演示:
MyThread类:
public class MyThread extends Thread {static int ticket = 0;//创建锁对象static Lock lock = new ReentrantLock();@Overridepublic void run() {while(true) {lock.lock();try {if(ticket == 1000) {break;} else {Thread.sleep(10);ticket++;System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}
测试类ThreadDemo8:
public class ThreadDemo8 {public static void main(String[] args) {//某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。//创建线程MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();//设置名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//启动线程t1.start();t2.start();t3.start();}
}
运行结果:
死锁:
概念:
死锁是多线程中的一种错误
举个例子:
两个人在一起吃饭,桌上只有一双筷子,有三个条件
①每次需要拿起筷子才能吃饭
②一次只能拿一只筷子
③拿到一双筷子后可以吃一口
在一个人拿到一只筷子,另一个人也拿到一只筷子时,这时候就发生了死锁,程序无法结束
注意:
关于死锁需要注意,在设计程序时尽量避免发生锁的嵌套
生产者和消费者(等待唤醒机制)
1.基本写法:
假如现在有一个厨师和一个食客,食客吃10份食物就饱了,当桌子上有食物时,食客就会吃一份,吃完通知厨师再做一份,如果没有食物,就等待。厨师在桌子上没有食物时,就再做一份,如果有食物则等待。
代码演示:
桌子Desk类:
public class Desk {//定义变量表示桌子上是否有食物 0:没有 1:有public static int foodFlag = 0;//定义变量存储食客还能吃几碗 食客最多能吃10碗 初始值为10public static int count = 10;//锁对象public static Object lock = new Object();
}
食客Foodie类:
public class Foodie extends Thread {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {//判断还能吃吗if(Desk.count == 0) {//不能吃了break;} else {//还能吃//判断桌子上有没有食物if(Desk.foodFlag == 0) {//如果没有食物//等待(调用锁对象的wait方法让食客线程阻塞)try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//如果有食物Desk.count--;if(Desk.count == 0) {System.out.println("食客吃完了一份食物,吃饱了");} else {System.out.println("食客吃完了一份食物,还能吃" + Desk.count + "份");}//将桌子上置为没有食物Desk.foodFlag = 0;//通知厨师Desk.lock.notifyAll();//唤醒这个锁对象内的所有线程}}}}}
}
厨师Cooker类:
public class Cooker extends Thread {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {//判断食客是否吃饱了if(Desk.count == 0) {//吃饱了break;} else {//没吃饱//判断桌子上是否有食物if(Desk.foodFlag == 1) {//有食物try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//没有食物System.out.println("厨师制作好了食物放到了桌子上");//将桌子置为有食物Desk.foodFlag = 1;//唤醒食客Desk.lock.notifyAll();//唤醒这个锁对象内的所有线程}}}}}
}
测试类Test:
public class Test {public static void main(String[] args) {//创建线程对象Foodie foodieThread = new Foodie();Cooker cookerThread = new Cooker();//启动线程foodieThread.start();cookerThread.start();}
}
运行结果:
2.利用阻塞队列:
假如现在有一个厨师和一个食客,食客吃10份食物就饱了,厨师可以不断的做好食物并放到窗口上,窗口上最多可以放3碗。当窗口上有食物时,食客就会吃,如果没有食物,就等待。
代码演示:
食客Foodie类:
public class Foodie extends Thread {ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {String food = queue.take();System.out.println("食客吃了一份" + food);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
厨师Cooker类:
public class Cooker extends Thread {ArrayBlockingQueue<String> queue;public Cooker(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {queue.put("面条");System.out.println("厨师放了一碗面条");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*假如现在有一个厨师和一个食客,厨师可以不断的做好食物并放到窗口上,窗口上最多可以放3碗。当窗口上有食物时,食客就会吃,如果没有食物,就等待。*/ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);Foodie foodieThread = new Foodie(queue);Cooker cookerThread = new Cooker(queue);foodieThread.start();cookerThread.start();}
}
运行结果:
(因为输出语句在锁的外面,所以输出结果不一定和真是数据传输一致)
练习题
练习一:
一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,
要求:用多线程模拟卖票过程并打印剩余电影票的数量
代码演示:
MyThread类:
public class MyThread extends Thread {//定义还剩多少票static int ticket = 1000;@Overridepublic void run() {while(true) {synchronized (MyThread.class) {//判断是否还有票if(ticket == 0) {//没票了break;} else {//有票//每次领取时间3000毫秒try {sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;if(ticket != 0) {System.out.println(getName() + "卖了一张票,还剩" + ticket + "张票");} else {System.out.println(getName() + "卖了一张票,票卖完了");}}}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,要求:用多线程模拟卖票过程并打印剩余电影票的数量*///创建线程对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();//设置名字t1.setName("窗口1");t2.setName("窗口2");//启动线程t1.start();t2.start();}
}
运行结果:
练习二:
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再发出
利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
代码演示:
MyThread类:
public class MyThread extends Thread {//剩余礼物数量static int gift = 100;@Overridepublic void run() {while (true) {synchronized (MyThread.class) {//判断礼物数量if(gift == 9) {//礼物数量小于10break;} else {//礼物数量不小于10gift--;System.out.println(getName() + "分发了一份礼物,还剩" + gift + "份");}}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再发出利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来*///创建线程对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();//设置名字t1.setName("分发员1");t2.setName("分发员2");//启动线程t1.start();t2.start();}
}
运行结果:
练习三:
同时开启了两个线程,共同获取1~100之间的所有数字
要求:输出所有的奇数
代码演示:
MyThread类:
public class MyThread extends Thread {static int num = 1;@Overridepublic void run() {while (true) {synchronized (MyThread.class) {if(num > 100) {break;} else {if(num % 2 == 1) {System.out.println(getName() + ":" + num);}num++;}}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*同时开启了两个线程,共同获取1~100之间的所有数字要求:输出所有的奇数*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.start();t2.start();}
}
运行结果:
练习四:
抢红包也用到了多线程
假设:100块,分成了3个包,现在有五个人去抢
其中,红包是共享数据 5个人是5条线程
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
代码演示:
MyThread类:
public class MyThread extends Thread {//红包个数static int count = 3;//红包中剩余金额static BigDecimal money = BigDecimal.valueOf(100);//红包金额最小值static final BigDecimal MIN = BigDecimal.valueOf(0.01);@Overridepublic void run() {//一人抢一次所以不用循环synchronized (MyThread.class) {//对红包数量判断if(count == 0) {//抢完了System.out.println(getName() + "没抢到红包");} else {//没抢完BigDecimal prize;if(count == 1) {//只剩一个红包prize = money;} else {//还剩多个红包Random r = new Random();double bounds = money.subtract(MIN.multiply(BigDecimal.valueOf(count - 1))).doubleValue();prize = BigDecimal.valueOf(r.nextDouble(bounds));if (prize.compareTo(MIN) == -1) {//prize小于MINprize = MIN;}}//设置保留两位小数,四舍五入prize = prize.setScale(2, RoundingMode.HALF_UP);System.out.println(getName() + "抢到了" + prize + "元");//从红包金额中减去抢到的金额money = money.subtract(prize);//红包个数减一count--;}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*抢红包也用到了多线程假设:100块,分成了3个包,现在有五个人去抢其中,红包是共享数据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("小B");t3.setName("小C");t4.setName("小D");t5.setName("小E");t1.start();t2.start();t3.start();t4.start();t5.start();}
}
运行结果:
练习五:
有一个抽奖池,该抽奖池中存放了奖励的金额,
该抽奖池中的奖项为: {10,5,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程) 随机从抽奖池中获取奖项元素并打印在控制台上
格式如下:
抽奖箱1产生了一个10元大奖
抽奖箱2产生了一个100元大奖
抽奖箱2产生了一个800元大奖
抽奖箱1产生了一个200元大奖
...
代码演示:
MyThread类:
public class MyThread extends Thread {ArrayList<Integer> list;public MyThread(ArrayList<Integer> list) {this.list = list;}@Overridepublic void run() {while((true)) {synchronized (MyThread.class) {if(list.isEmpty()) {//集合为空break;} else {//集合不为空Collections.shuffle(list);int prize = list.remove(0);System.out.println(getName() + "产生了一个" + prize + "元大奖");}}//锁外睡10毫秒,避免结果只出现一个线程try {sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为:{10,5,20,50,100,200,500,800,2,80,300,700}创建两个抽奖箱(线程)随机从抽奖池中获取奖项元素并打印在控制台上格式如下:抽奖箱1产生了一个10元大奖抽奖箱2产生了一个100元大奖抽奖箱2产生了一个800元大奖抽奖箱1产生了一个200元大奖...*///创建抽奖池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();}
}
运行结果:
练习五Pro1:
在上一题(练习五)的基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)
格式如下:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项
分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项
分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
代码演示:
MyThread类:
public class MyThread extends Thread {ArrayList<Integer> list;int max = 0;int sum = 0;public MyThread(ArrayList<Integer> list) {this.list = list;}public String boxToString(ArrayList<Integer> box) {StringBuffer sb = new StringBuffer();for (int i = 0; i < box.size(); i++) {if (i != box.size() - 1) {sb.append(box.get(i) + ",");} else {sb.append(box.get(i));}}return sb.toString();}@Overridepublic void run() {ArrayList<Integer> box = new ArrayList<>();while ((true)) {synchronized (com.han.thread.test5.MyThread.class) {if (list.isEmpty()) {//集合为空System.out.println("在此次抽奖过程中," + getName() + "总共产生了" + box.size() + "个奖项\n" +"分别为:" + boxToString(box) + "最高奖项为" + max + "元,总计额为" + sum + "元");break;} else {//集合不为空Collections.shuffle(list);int prize = list.remove(0);box.add(prize);sum = sum + prize;if (prize > max) {max = prize;}//System.out.println(getName() + "产生了一个" + prize + "元大奖");}}//锁外睡10毫秒,避免结果只出现一个线程try {sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
测试类Test:
public class Test {public static void main(String[] args) {/*在上一题(练习五)的基础上继续完成如下需求:每次抽的过程中,不打印,抽完时一次性打印(随机)格式如下:在此次抽奖过程中,抽奖箱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();}
}
运行结果:
练习五Pro2:
在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)
格式如下:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项
分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项
分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
代码演示:
MyCallable类:
public class MyCallable implements Callable<Integer> {ArrayList<Integer> list;public MyCallable(ArrayList<Integer> list) {this.list = list;}public String boxToString(ArrayList<Integer> box) {StringBuffer sb = new StringBuffer();for (int i = 0; i < box.size(); i++) {if (i != box.size() - 1) {sb.append(box.get(i) + ",");} else {sb.append(box.get(i));}}return sb.toString();}@Overridepublic Integer call() throws Exception {int max = 0;int sum = 0;ArrayList<Integer> box = new ArrayList<>();while ((true)) {synchronized (com.han.thread.test5.MyThread.class) {if (list.isEmpty()) {//集合为空System.out.println("在此次抽奖过程中," + Thread.currentThread().getName() + "总共产生了" + box.size() + "个奖项\n" +"分别为:" + boxToString(box) + "最高奖项为" + max + "元,总计额为" + sum + "元");break;} else {//集合不为空Collections.shuffle(list);int prize = list.remove(0);box.add(prize);sum = sum + prize;if (prize > max) {max = prize;}//System.out.println(getName() + "产生了一个" + prize + "元大奖");}}//锁外睡10毫秒,避免结果只出现一个线程Thread.sleep(10);}if(box.isEmpty()) {return null;} else {return Collections.max(box);}}}
测试类Test:
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {/*在上一题基础上继续完成如下需求:每次抽的过程中,不打印,抽完时一次性打印(随机)格式如下:在此次抽奖过程中,抽奖箱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();//输出结果int max1 = ft1.get();int max2 = ft2.get();if (max1 > max2) {System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为" + max1 + "元");} else {System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为" + max2 + "元");}}
}
运行结果:
线程池
核心原理:
①创建一个池子,池子中是空的
②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
③但是如果提交任务时,池子中没有空闲的线程,也无法创建新的线程,任务就会排队等待
创建方法:
代码演示:
MyRunnable类:
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 1; i <= 1000; i++) {System.out.println(Thread.currentThread().getName() + "-" + i);}}
}
测试类ThreadPoolDemo1:
public class ThreadPoolDemo1 {public static void main(String[] args) throws InterruptedException {//创建一个没有上限的线程池(上限二十一亿多,但是创建不了这么多机器就承受不住)ExecutorService pool1 = Executors.newCachedThreadPool();System.out.println("无上限线程池");pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());//销毁线程池pool1.shutdown();//创建一个有上线的线程池ExecutorService pool2 = Executors.newFixedThreadPool(3);System.out.println("有上限线程池");pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());//销毁线程池pool2.shutdown();}
}
自定义线程池
核心原理:
①创建一个池子,池子中是空的
②有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程
不断的提交任务,会有以下三个临界点
①当核心线程满时,再提交任务就会排队
②当核心线程满,队伍满时,会创建临时线程
③当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
任务拒绝策略:
创建代码演示:
public class ThreadPoolDemo2 {public static void main(String[] args) {/*ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略)参数一:核心线程数量 不能小于0参数二:最大线程数量 不能小于0,最大数量>=核心线程数量参数三:空闲线程最大存活时间 不能小于0参数四:时间单位 用TimeUnit指定参数五:任务队列 不能为null参数六:创建线程工厂 不能为null参数七:任务的拒绝策略 不能为null*/ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,6,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}
}
线程池多大合适
前置概念:
最大并行数:
如果电脑是四核八线程,那么最大并行数就是8
我的电脑是八核十六线程,所以最大并行数是16
1.CPU密集型运算:
计算比较多,读取本地文件或者数据库的操作比较少
线程池大小:
这种情况线程池大小最好为最大并行数+1=17,多出来那一个用来候补,,在遇到问题时顶上去
2.I/O密集型运算:
读取本地文件或者数据库的操作比较多
线程池大小:
有下面公式求出
这里期望CPU利用率我们设为100%,假如要从本地文件中读取两个数据并进行相加,获取数据由硬盘完成,不计算在CPU计算时间中,假设读取数据用1秒,CPU计算用1秒,那么总时间就为2秒,线程池大小就等于16(最大并行数)* 100% * 2/1 = 32
CPU计算时间可以通过thread dump工具类获取,然后计算