# 多线程
- 线程:指操作系统中能够进行运算调度的最小单位。它被包含在进程中,是进程的实际运作单位
- 进程:是程序的基本执行实体
- 作用:有了多线程,就可以让程序同时做多件事情,以提高效率
# 并发和并行
- 并发:在同一时刻,有多个指令在单个CPU上交替执行
- 并行:在同一时刻,有多个指令在多个cpu上同时执行
# 多线程的实现方式
- 继承
Thread类的方式进行实现 - 实现
Runnable接口进行实现 - 利用
Callable接口和Future接口方式实现
# 多线程三种实现方式对比
| 优点 | 缺点 | |
|---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可拓展性较差,不能再继承其他类 |
实现Runnable接口 | 拓展性强,实现该接口的同时还可以继承其他类 | 编程相对复杂,不能直接使用Thread类中的方法 |
实现Callable接口 | 拓展性强,实现该接口的同时还可以继承其他类 | 编程相对复杂,不能直接使用Thread类中的方法 |
# 继承Thread类的方式进行实现
package Multithreading;
//多线程
public class Main {
public static void main(String[] args) {
//创建子类的对象
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
myThread.setName("线程一");
myThread1.setName("线程二");
//开启多线程`start()`方法表示开启线程
//线程1和2交替执行
myThread.start();
myThread1.start();
}
}
//多线程的实现方式
//继承`Thread`类的方式进行实现
class MyThread extends Thread {
//重写`run()`方法 当类调用`start()`方法时,`run()`方法就会执行
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "MyThread");
}
}
}
# 多线程的实现方式二
实现Runnable接口进行实现
package Multithreading;
public class Multithreading2 {
public static void main(String[] args) {
//多线程的第二种启动方式
//3.创建自己类的对象
MyRunnable myRunnable = new MyRunnable();
//4.创建一个Thread对象,把对象传给Thread的构造方法
Thread thread = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread.setName("线程1");
thread2.setName("线程2");
//5.开启多线程
thread.start();
thread2.start();
}
}
//1.自己定义一个类实现Runnable接口,重写`run()`方法,
class MyRunnable implements Runnable {
// 2.重写里面的run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取当前线程的对象
Thread thread = Thread.currentThread();
//获取当前线程的名字
System.out.println(thread.getName() + "-->" + i);
}
}
}
# 多线程的实现方式三
利用Callable接口和Future接口方式实现
- 前面两种接口均无返回值因此不能获取线程中的执行结果
- 特点:可以获取多线程执行的结果
package Multithreading;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Multithreading3 {
// 利用`Callable`接口和`Future`接口方式实现
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建多线程的对象,表示多线程应该执行的任务
MyCallable myCallable = new MyCallable();
//4.创建一个`Future`对象,用来管理myCallable 多线程运行的结果
//Future是一个接口,因此需要创建其实现类的对象FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
FutureTask<Integer> futureTask2 = new FutureTask<>(myCallable);
//5.创建Thread对象
Thread thread = new Thread(futureTask);
Thread thread1 = new Thread(futureTask2);
thread.setName("线程1");
thread1.setName("线程2");
//6.启动多线程
thread.start();
thread1.start();
//7.获取多线程运行结果
Integer integer = futureTask.get();
Integer integer1 = futureTask2.get();
System.out.println("多线程1运行结果" + integer);
System.out.println("多线程2运行结果" + integer1);
}
}
//1.创建一个类,实现`Callable`接口
//泛型表示结果的类型
class MyCallable implements Callable<Integer> {
//2.重写`call()`方法,该方法有返回值,这个返回值就表示多线程运行的结果
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return sum;
}
}
# Thread类常见的成员方法
| 方法名称 | 说明 |
|---|---|
String getName() | 返回此线程的名称 |
void setName() | 设置此线程的名称 |
void start() | 启动此线程 |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long millis) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 (最小是1,最大是10,默认为5)优先级越大抢占到cpu的概率越高 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置线程为守护线程(备胎线程) |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
package Multithreading;
public class ThreadText {
public static void main(String[] args) throws InterruptedException {
//Thread类中的成员方法
//使用同样的方法设置名字
//可以用setName()和构造方法设置名字
Thread thread = new MyThread1("线程1");
thread.start();
//3.获取当前线程的名字
//细节:java虚拟机启动时,会自动启用多条线程
//其中有一条线程就叫main线程
String name = Thread.currentThread().getName();
System.out.println("当前执行线程" + name);//main
//4.线程休眠
//那条线程执行到这个方法,那么那条线程就会在这里停留对应的时间
//方法的参数就是停留的时间 单位是毫秒
//当时间到了之后线程会自动醒来,继续执行下面的代码
//使main线程休眠
System.out.println("main线程开始休眠");
Thread.sleep(1);
System.out.println("main线程休眠结束");
//5.获取线程优先级
System.out.println(thread.getPriority());//5
//main线程的优先级
System.out.println(Thread.currentThread().getPriority());//5
Thread thread1 = new MyThread1("飞机");
Thread thread2 = new MyThread1("坦克");
//设置优先级
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
//开启线程
thread1.start();
thread2.start();
//守护线程
//当所有的非守护线程(女神)执行完毕后,守护线程(备胎)即使没执行完也会结束
Thread thread3 = new MyThread2("女神");
Thread thread4 = new MyThread3("备胎");
//设置为守护线程
thread4.setDaemon(true);
thread3.start();
thread4.start();
//出让/礼让线程
//出让当前cpu的执行权给其他线程执行
Thread.yield();
System.out.println("main线程继续执行");
//插队线程
//将女神线程插队,抢占cpu的执行权 等女神线程执行完毕再执行main线程
thread3.join();
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
class MyThread1 extends Thread {
public MyThread1() {
}
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
//1.设置线程的名称
//如果没有给线程设置名字,线程也是有默认名字的
//格式:Thread-X(X:序号,从0开始)
System.out.println(getName() + "开始休眠");
System.out.println(getName() + "结束休眠");
//2.返回此线程的名称
System.out.println(getName());//默认为Thread-1
}
}
//女神线程
class MyThread2 extends Thread {
public MyThread2(String name) {
super(name);
}
public MyThread2() {
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
//备胎线程
class MyThread3 extends Thread {
public MyThread3() {
}
public MyThread3(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
# 线程的生命周期

# 线程安全问题
- 需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票
- 请设计一个程序模拟该电影院卖票
package Multithreading;
public class ThreadText1 {
public static void main(String[] args) {
//线程安全问题
//创建售票员
Seller seller = new Seller("售票员1");
Seller seller1 = new Seller("售票员2");
Seller seller3 = new Seller("售票员3");
//开启线程
seller.start();
seller1.start();
seller3.start();
}
}
//售票员类
class Seller extends Thread {
//票号
//static 表示所有的类都共享ticket这个数据
static int ticket = 0;
public Seller() {
}
public Seller(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "出售第" + ticket + "张票");
} else break;
}
}
}
# 卖票引发的安全问题
相同的票出现了多次
出现了超出范围的票
根本原因: cpu的执行权有可能随时会被其他进程抢走
- 解决方法:利用同步代码块**
synchronized** 使同步代码块里面的代码是轮流执行
package Multithreading;
public class ThreadText1 {
public static void main(String[] args) {
//线程安全问题
//创建售票员
Seller seller = new Seller("售票员1");
Seller seller1 = new Seller("售票员2");
Seller seller3 = new Seller("售票员3");
//开启线程
seller.start();
seller1.start();
seller3.start();
}
}
//票类-实现私有化构造方法 防止外部实例化该类
class TicketUtis {
//票的数量
//static 表示所有的类都共享ticket这个数据
static int MAX_TICKET_NUM = 100;
//当前票的编号
static Integer ticketNum = 0;
//私有化构造方法防止外部实例化该类
private TicketUtis() {
}
//获取票号
public static Integer getTicketNum() {
//利用同步代码块 使同步代码块里面的代码是轮流执行
//创建锁对像保证对象是唯一的
//TicketUtis.class 表示当前类的字节码 只要是同一个类那么就是唯一的
synchronized (TicketUtis.class) {
if (ticketNum >= MAX_TICKET_NUM) {
System.out.println("票卖完了");
return null;
} else {
ticketNum++;
return ticketNum;
}
}
}
}
//售票员类
class Seller extends Thread {
public Seller() {
}
public Seller(String name) {
super(name);
}
@Override
//设置同步代码块
public void run() {
Integer ticketNum = null;
while ((ticketNum = TicketUtis.getTicketNum()) != null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "出售第" + ticketNum + "张票");
}
}
}
# 同步方法
- 同步方法:就是把
synchronized加在方法上 - 作用将该方法的所有代码都锁起来,轮流执行
- 如果方法是非静态的,则锁对象是this
- 如果方法是静态(static)的,则锁对象是当前类的字节码对象 当前类.class
package Multithreading;
public class ThreadText1 {
public static void main(String[] args) {
//线程安全问题
//创建售票员
Seller seller = new Seller("售票员1");
Seller seller1 = new Seller("售票员2");
Seller seller3 = new Seller("售票员3");
//开启线程
seller.start();
seller1.start();
seller3.start();
}
}
//售票员类
class Seller extends Thread {
//票的数量
//static 表示所有的类都共享ticket这个数据
static int MAX_TICKET_NUM = 100;
//当前票的编号
static Integer ticketNum = 0;
public Seller() {
}
public Seller(String name) {
super(name);
}
//同步方法
//如果方法是非静态的,则锁对象是this
//如果方法是静态(static)的,则锁对象是当前类的字节码对象 当前类.class
public static synchronized Integer getTicketNum() {
//利用同步代码块 使同步代码块里面的代码是轮流执行
//TicketUtis.class 表示当前类的字节码 只要是同一个类那么就是唯一的
if (ticketNum >= MAX_TICKET_NUM) {
System.out.println("票卖完了");
return null;
} else {
ticketNum++;
return ticketNum;
}
}
@Override
//设置同步代码块
public void run() {
Integer ticketNum = null;
while ((ticketNum = getTicketNum()) != null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "出售第" + ticketNum + "张票");
}
}
}
# lock锁
lock锁是JDK5.0之后出现的一种新的锁机制- 可以实现同步代码手动上锁的功能
- **
lock是接口不能直接实例化,这里采用他的实现类ReentrantLock**来实例化
package Multithreading;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadText1 {
// -----
//定义锁对象
static Lock lock = new ReentrantLock();
public static Integer getTicketNum() {
//上锁
lock.lock();
//利用同步代码块 使同步代码块里面的代码是轮流执行
//TicketUtis.class 表示当前类的字节码 只要是同一个类那么就是唯一的
try {
if (ticketNum >= MAX_TICKET_NUM) {
System.out.println("票卖完了");
return null;
} else {
ticketNum++;
return ticketNum;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//写在finally里面 表示无论怎样代码执行完一定会释放锁
//解锁
lock.unlock();
}
return null;
}
}
# 死锁
- 死锁:是指两个或者两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们将never结束。
- 死锁产生的条件:
- 互斥条件:指进程对资源进行排他性使用,即一个进程使用一个资源时,其他进程不可使用这些资源。
- 请求和保持条件:指进程已经保持至少一个资源,在请求其他资源时,资源请求被允许。
- 不剥夺条件:指进程已经获得的资源,在未使用完之前,不能被剥夺,只能自己使用。
- 循环等待条件:指进程之间形成一种头尾相接的循环等待资源关系。
- 解决死锁:避免使用锁嵌套的方式编写代码
# 等待唤醒机制(消费者和生产者模式)
- 等待唤醒机制是一个十分经典额多线程协作模式
| 方法名称 | 说明 |
|---|---|
void wait() | 当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法唤醒线程 |
void notify() | 唤醒一个正在等待的线程(随机) |
void notifyAll() | 唤醒所有正在等待的线程 |
package Multithreading;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadText2 {
//等待唤醒机制
public static void main(String[] args) {
//定义吃货
Thread eater = new Thread(new Eater(), "吃货1");
Thread eater1 = new Thread(new Eater(), "吃货2");
Thread eater2 = new Thread(new Eater(), "吃货3");
//定义厨师
Thread cook = new Thread(new Cook(), "厨师");
//开启线程
eater.start();
eater1.start();
eater2.start();
cook.start();
}
}
class Desk implements FoodStatus {
//设置锁
public static final Lock lock = new ReentrantLock();
//食物数量
public static Integer count = 0;
//最大的食物数量
public static Integer maxCount = 10;
//食物状态
public static int status = FoodStatus.EMPTY;
}
//定义吃货
class Eater implements Runnable {
public Eater() {
}
//吃货吃东西方法
@Override
public void run() {
while (Desk.maxCount > 0) {
synchronized (Desk.lock) {
if (Desk.status == FoodStatus.EMPTY) {
//没菜就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else if (Desk.status == FoodStatus.FULL) {
//有菜则吃菜
Desk.maxCount--;
System.out.println(Thread.currentThread().getName() + "正在吃第" + Desk.count + "个菜" + "还能吃" + Desk.maxCount + "个菜");
Desk.count--;
if (Desk.count == 0) {
//如果菜没了 则将状态设置为空
Desk.status = FoodStatus.EMPTY;
//通知厨师做菜
Desk.lock.notifyAll();
}
}
}
}
}
}
//定义厨师
class Cook implements Runnable {
//厨师每次做菜的数量
private static Integer count = 10;
public Cook() {
}
//厨师做菜
@Override
public void run() {
while (Desk.maxCount > 0) {
synchronized (Desk.lock) {
//没有菜就做菜
if (Desk.status == FoodStatus.EMPTY) {
System.out.println(Thread.currentThread().getName() + "正在做" + count + "个菜");
Desk.count += count;
Desk.status = FoodStatus.FULL;
//通知吃货
Desk.lock.notifyAll();
} else if (Desk.status == FoodStatus.FULL) {
//有菜则等待
//让当前线程跟锁绑定
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
# 阻塞队列(ArrayBlockingQueue)
package Multithreading;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadText4 {
public static void main(String[] args) {
//阻塞队列
//定义阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
//创建厨师
Cook1 cook = new Cook1(queue);
//创建吃货
Eater1 eater = new Eater1(queue);
//创建线程
Thread thread = new Thread(cook);
Thread thread3 = new Thread(eater);
//开启线程
thread.start();
thread3.start();
}
}
//定义厨师类
class Cook1 implements Runnable {
private ArrayBlockingQueue<String> queue;
//在实例化对象的时候传递阻塞队列
public Cook1(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
//重写run方法
@Override
public void run() {
//循环生产
while (true) {
try {
//生产
queue.put("一个汉堡");
System.out.println("厨师生产了一个汉堡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//吃货 类
class Eater1 implements Runnable {
private ArrayBlockingQueue<String> queue;
//在实例化对象的时候传递阻塞队列
public Eater1(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
//重写run方法
@Override
public void run() {
//循环消费
while (true) {
try {
//消费
String food = queue.take();
System.out.println("吃货消费了" + food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
# 线程的状态
注意
在java当中只定义了6种状态
没有运行状态,该状态交给操作系统运行
新建状态(NEW):创建线程对象
就绪状态(RUNNABLE):start()方法调用后进入就绪状态
阻塞状态(BLOCKED):无法获得锁对象
等待状态(WAITING):调用wait()方法进入等待状态
计时等待状态(TIMED_WAITING):调用sleep()方法进入计时等待状态
结束状态(TERMINATED):线程全部代码执行完毕

# 多线程综合练习
# 送礼品
- 需求:有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。
- 利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来.
package Multithreading;
public class ThreadText5 {
public static void main(String[] args) {
//送礼品问题
//- 需求:有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。
//- 利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来.
//定义线程对象
Thread user1 = new Thread(new GiftUser(), "张三");
Thread user2 = new Thread(new GiftUser(), "李四");
//开启线程
user1.start();
user2.start();
}
}
//礼品发送人
class GiftUser implements Runnable {
//剩余礼品数
//最开始有100个礼品
public static int giftNum = 100;
public GiftUser() {
}
@Override
public void run() {
//发送礼品
//当礼品剩余10个停止发出
//注意判断尽量写在synchronized中 写在while条件里可能使代码加入while循环但在synchronized等待的情况
while (true) {
synchronized (GiftUser.class) {
//直接在synchronized中编写条件
if (giftNum <= 10) break;
giftNum--;
System.out.println(Thread.currentThread().getName() + "正在发送礼品" + "剩余" + giftNum + "个");
}
}
}
}
# 打印奇数数字
- 同时开启两个线程,共同获取1-100之间的所有数字。
- 要求:将输出所有的奇数。
package Multithreading;
public class ThreadText6 {
public static void main(String[] args) {
//- 同时开启两个线程,共同获取1-100之间的所有数字。
//- 要求:将输出所有的奇数。
//定义线程对象
Thread thread1 = new Thread(new PrintOdd(), "线程1");
Thread thread2 = new Thread(new PrintOdd(), "线程2");
//开启线程
thread1.start();
thread2.start();
}
}
//打印奇数数字
class PrintOdd implements Runnable {
public static int Maxnum = 100;
@Override
public void run() {
while (true) {
synchronized (PrintOdd.class) {
System.out.println("当前数" + " " + Maxnum);
if (Maxnum <= 0) break;
if (Maxnum % 2 == 1) {
System.out.println(Thread.currentThread().getName() + "正在打印奇数" + Maxnum);
}
Maxnum--;
}
}
}
}
# 抢红包也用到了多线程。
- 假设:100块,分成了3个包,现在有5个人去抢。
- 其中,红包是共享数据。
- 5个人是5条线程。
- 打印结果如下:
- XXX抢到了XXX元
- XXX抢到了XXX元
- XXX抢到了XXX元
- XXX没抢到
- XXX没抢到
package Multithreading;
public class ThreadText7 {
public static void main(String[] args) {
}
//抢红包问题
//定义线程对象
Thread thread1 = new Thread(new Reader(), "张三");
Thread thread2 = new Thread(new Reader(), "李四");
Thread thread3 = new Thread(new Reader(), "王五");
Thread thread4 = new Thread(new Reader(), "赵六");
Thread thread5 = new Thread(new Reader(), "钱七");
//开启线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
//抢红包
class Reader implements Runnable {
//剩余金额
public static BigDecimal money = BigDecimal.valueOf(100);
//剩余红包数
public static int count = 3;
//每个红包最小金额(精确小数)
public final static BigDecimal minMoney = BigDecimal.valueOf(0.01);
//获取金额函数
/**
* @param money BigDecimal 剩余金额
* @param count int 剩余红包数
* @return BigDecimal 抢到的金额
*/
public BigDecimal getMoney(BigDecimal money, int count) {
if (count == 0) return BigDecimal.valueOf(-1);
else if (count == 1) return money;
// 生成随机数
else {
Random r = new Random();
double bound = money.subtract(BigDecimal.valueOf(count - 1).multiply(minMoney)).doubleValue();
BigDecimal getdMoney = BigDecimal.valueOf(r.nextDouble(bound));
if (getdMoney.compareTo(minMoney) < 0) getdMoney = minMoney;
//保留两位小数
return getdMoney.setScale(2, RoundingMode.HALF_UP);
}
}
@Override
public void run() {
synchronized (Reader.class) {
BigDecimal getdMoney = getMoney(money, count);
if (getdMoney.compareTo(BigDecimal.valueOf(-1)) == 0) {
System.out.println(Thread.currentThread().getName() + "没抢到");
} else {
System.out.println(Thread.currentThread().getName() + "抢到了" + getdMoney + "元");
money = money.subtract(getdMoney);
count--;
}
}
}
}
# 抽奖箱抽奖
- 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80 ,300,700};
- 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”“抽奖箱2随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
- 每次抽出一个奖项就打印一个(随机)
- 抽奖箱1 又产生了一个 10 元大奖
- 抽奖箱1 又产生了一个 100 元大奖
- 抽奖箱1 又产生了一个 200 元大奖
- 抽奖箱1 又产生了一个 800 元大奖
- 抽奖箱2 又产生了一个 700 元大奖
package Multithreading;
public class ThreadText8 {
public static void main(String[] args) {
//抽奖池
Thread thread1 = new Thread(new Pool(), "抽奖1");
Thread thread2 = new Thread(new Pool(), "抽奖2");
thread1.start();
thread2.start();
}
}
//抽奖池
class Pool implements Runnable {
public static ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 5, 10, 20, 50, 80, 100, 200, 300, 500, 700, 800));
@Override
public void run() {
while (true) {
synchronized (Pool.class) {
if (list.isEmpty()) {
System.out.println("抽奖池已空");
break;
} else {
//抽奖
//打乱数组
Collections.shuffle(list);
//获取第一个
int prize = list.getFirst();
System.out.println(Thread.currentThread().getName() + "抽到了" + prize + "元");
//将抽中的奖品移除
list.removeFirst();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
# 抽奖池抽奖2 记录奖品
- 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{1,5,20,58,108,208,580,880,2,80,300,708};
- 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”随机从抽奖池中获取奖项元素并打印在控制台上,
- 格式如下:
- 每次抽的过程中,不打印,抽完时一次性打印(随机)在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
- 分别为:10,20,100,500,2,300最高奖项为30元,总计额为932元在此次抽奖过程中,抽奖箱2总共产生了6个奖项。 分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
package Multithreading;
public class ThreadText9 {
public static void main(String[] args) {
//抽奖池
Thread thread1 = new Thread(new Pool(), "抽奖1");
Thread thread2 = new Thread(new Pool(), "抽奖2");
thread1.start();
thread2.start();
}
}
class Pool implements Runnable {
public static ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 5, 10, 20, 50, 80, 100, 200, 300, 500, 700, 800));
@Override
public void run() {
ArrayList<Integer> result = new ArrayList<>();
while (true) {
synchronized (Pool.class) {
if (list.isEmpty()) {
System.out.println("抽奖池已空");
break;
} else {
//抽奖
//打乱数组
Collections.shuffle(list);
//获取第一个
int prize = list.getFirst();
//将奖品放入result集合中
result.add(prize);
//将抽中的奖品移除
list.removeFirst();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//循环结束打印结果
//当前奖项数组
//求最大值
Integer[] array = result.toArray(new Integer[0]);
Arrays.sort(array);
int max = array[array.length - 1];
//获取总和
int sum = result.stream().mapToInt(Integer::intValue).sum();
System.out.println("在此次抽奖过程中" + Thread.currentThread().getName() + "产生了" + array.length + "个奖项,最高奖项为" + max + "总计金额为" + sum);
}
}
# 抽奖池3 多线程之间的比较
- 在上一题基础上继续完成如下需求:
- 在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
- 在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700
- 最高奖项为800元,总计额为1835元
- 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
核心逻辑:获取线程运行的结果,然后进行比较,获取最大值,然后打印出来。
package Multithreading;
public class ThreadText10 {
public static void main(String[] args) {
//抽奖池
PoolReturn poolReturn = new PoolReturn();
PoolReturn poolReturn2 = new PoolReturn();
////4.创建一个`Future`对象,用来管理多线程运行的结果
FutureTask<ArrayList<Integer>> futureTask = new FutureTask<>(poolReturn);
FutureTask<ArrayList<Integer>> futureTask2 = new FutureTask<>(poolReturn2);
//创建线程
Thread thread1 = new Thread(futureTask, "抽奖1");
Thread thread2 = new Thread(futureTask2, "抽奖2");
//开启线程
thread1.start();
thread2.start();
//获取多线程运行结果
ArrayList<Integer> integers = futureTask.get();
ArrayList<Integer> integers2 = futureTask2.get();
int integersMax = todoGet(integers, thread1);
int integers2Max = todoGet(integers2, thread2);
int max = Math.max(integersMax, integers2Max);
System.out.println("最大奖项为" + ((integersMax == max) ? "奖池1的" : "奖池2的") + max);
}
//线程返回值处理
public static int todoGet(ArrayList<Integer> list, Thread thread) {
//当前奖项数组
//求最大值
if (list.isEmpty()) return 0;
int max = Collections.max(list);
//获取总和
int sum = list.stream().mapToInt(Integer::intValue).sum();
System.out.println("在此次抽奖过程中" + thread.getName() + "产生了" + list.size() + "个奖项,最高奖项为" + max + "总计金额为" + sum);
return max;
}
}
//抽奖池 线程返回抽奖结果
class PoolReturn implements Callable<ArrayList<Integer>> {
public static ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 5, 10, 20, 50, 80, 100, 200, 300, 500, 700, 800));
@Override
public ArrayList<Integer> call() throws Exception {
ArrayList<Integer> result = new ArrayList<>();
while (true) {
synchronized (Pool.class) {
if (list.isEmpty()) {
System.out.println("抽奖池已空");
break;
} else {
//抽奖
//打乱数组
Collections.shuffle(list);
//获取第一个
int prize = list.getFirst();
//将奖品放入result集合中
result.add(prize);
//将抽中的奖品移除
list.removeFirst();
}
}
}
return result;
}
}
# 线程池
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
Executors线程池的工具类通过调用方法返回不同类型的线程池对象
| 方法名称 | 说明 |
|---|---|
public status ExecutorService newFixedThreadPool(int nThreads) | 创建一个有上线的线程池 |
public static ExecutorService newCachedThreadPool() | 创建一个没有上线的线程池 最多int的最大值 |
public class ThreadPool {
public static void main(String[] args) {
//线程池
// 1.创建一个有上线的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(1);
//2.提交任务
threadPool.submit(new Pool());
threadPool.submit(new Pool());
threadPool.submit(new Pool());
threadPool.submit(new Pool());
threadPool.submit(new Pool());
//销毁线程(一般不需要)
// threadPool.shutdown();
}
}
# 自定义线程池(new ThreadPoolExecutor())
new ThreadPoolExecutor()构造函数的参数
- 参数1:核心线程的数量 不能小于0
- 参数2:最大线程的数量 不能小于0,最大数量>=核心线程数
- 参数3:线程空闲时间 不能等于0
- 参数4:线程空闲时间的单位 用
TimeUnit指定 - 参数5:线程池的阻塞队列 不能为null
- 参数6:创建线程的工厂 不能为null
- 参数7:拒绝策略 不能为null
细节:
- 当线程池满时,再提交任务就会排队
- 当核心线程满并且队伍也满时,才会创建临时线程
- 当核心和临时线程池满,并且队伍也满,就会执行拒绝策略
public class ThreadPool {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
3,//核心线程的数量 不能小于0
5,//最大线程数量 不能小于0 最大数量>=核心线程数
60,//线程存活时间
TimeUnit.SECONDS,//线程存活时间的单位
new ArrayBlockingQueue<>(3),//阻塞队列
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
threadPool.submit(new Pool());
threadPool.submit(new Pool());
threadPool.submit(new Pool());
threadPool.submit(new Pool());
threadPool.submit(new Pool());
}
}
# 线程池大小
# 最大并行数
- 线程池的最大并行数可以根据java运行环境的可用处理器数目来决定
- 可以
public class ThreadPool {
public static void main(String[] args) {
//线程池多少才合适
//获取java虚拟机的运行环境
Runtime runtime = Runtime.getRuntime();
//返回可用处理器数目
//线程池的最大并行数可以根据java运行环境的可用处理器数目来决定
int availableProcessors = runtime.availableProcessors();
System.out.println("当前java虚拟机的处理器数量为" + availableProcessors);//32
System.out.println("当前java虚拟机的最大内存为" + runtime.maxMemory() / 1024 / 1024 + "MB");
}
}
# 线程池多大合适
# cpu密集型运算
线程池数量=最大并行数+1
# I/O密集型运算
线程池数量=最大并行数量期望cpu利用率总时间(cpu计算时间+等待时间)/cpu计算时间
- 使用
thread dump插件计算