有了多线程,我们就可以让程序同时做多件事情
作用:
提高效率
应用场景:
只要想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器...
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式:
①继承Thread类的方式进行实现
实现步骤:
1.自己定义一个类继承Thread
2.重写run方法
3.创建子类对象,并启动线程
代码演示:
MyThread类(继承Thread):
public class MyThread extends Thread {
@Override
public 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.创建类继承Thread
2.重写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{
@Override
public 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> {
@Override
public 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 {
@Override
public 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 {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
MyThread2:
public class MyThread2 extends Thread {
@Override
public 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;
//锁对象,必须是唯一的
@Override
public 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;
@Override
public 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();
@Override
public 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碗 初始值为10
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
食客Foodie类:
public class Foodie extends Thread {
@Override
public 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 {
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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;
@Override
public 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;
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
//判断礼物数量
if(gift == 9) {
//礼物数量小于10
break;
} else {
//礼物数量不小于10
gift--;
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;
@Override
public 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);
@Override
public 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小于MIN
prize = 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;
}
@Override
public 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();
}
@Override
public 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();
}
@Override
public 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 {
@Override
public 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工具类获取,然后计算