一、概念
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
简单理解:应用软件中互相独立,可以同时运行的功能
进程
进程是程序的基本执行实体。
多线程中的两个概念:并发和并行
并发
在同一时刻,有多个指令在单个CPU上交替执行
并行
在同一时刻,有多个指令在多个CPU上同时执行
二、多线程的实现方式
优点 | 缺点 | |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性较差,不能再继承其他的类 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
1. 继承Thread类的方式进行实现
public class MyThread extends Thread{
@Override
public void run() {
// 书写线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":Hello, World!");
}
}
}
public class ThreadDemo {
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();
}
}
2. 实现Runnable接口的方式进行实现
public class MyThread02 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 获取当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":Hello, World!");
}
}
}
public class ThreadDemo02 {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式
* 1. 自己定义一个类实现Runnable接口
* 2. 重写里面的run方法
* 3. 创建自己的这个类的对象
* 4. 创建一个Thread类的对象,启动线程
* */
// 创建MyThread02的对象
// 表示多线程要执行的任务
MyThread02 mt = new MyThread02();
// 创建线程对象
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
// 给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
// 启动线程
t1.start();
t2.start();
}
}
3. 利用Callable接口和Future接口方式实现
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 求1~100之间的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式
* 特点:可以获取到多线程执行的结果
* 1. 创建一个MyCallable实现Callable接口
* 2. 重写call(是有返回值的,表示多线程允许的结果)
*
* 3. 创建MyCallable的对象(表示多线程要执行的任务)
* 4. 创建FutureTask的对象(作用:管理多线程运行的结果)
* 5. 创建Thread类的对象(表示线程)
* 6. 启动线程
* */
// 创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
// 创建FutureTask的对象(作用:管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
// 创建线程的对象
Thread t1 = new Thread(ft);
// 启动线程
t1.start();
// 获取多线程运行的结果
Integer result = ft.get();
System.out.println(result); // 5050
}
}
三、常见的成员方法
方法名称 | 说明 |
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插让线程/插队线程 |
示例一:前四个方法
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + "@" + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
String getName() 返回此线程的名称
细节:1. 如果我们没有给线程设置名字,线程也是有默认的名字的。格式:Thread-X(X:序号, 从0开始)
void setName(String name) 设置线程的名字
细节2:如果我们要给线程设置名字,可以用set方法进行设置,也可以使用构造方法设置
static Thread currentThread() 获取当前线程的对象
细节3:当JVM虚拟机启动之后,会自动启动多条线程,其中有一条线程就叫做main线程
它的作用就是去调用main方法,并执行里面的代码。
在以前,我们写的代码,其实都是运行在main线程当中
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
细节4:①哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间;
②方法的参数:表示睡眠的时间,单位毫秒;
③当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
*/
// 1. 创建线程的对象
MyThread t1 = new MyThread("飞机");
MyThread t2 = new MyThread("坦克");
// 2. 开启线程
t1.start();
t2.start();
// 哪条线程执行到这个方法,此时获取的就是哪条线程的对象
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);
System.out.println("11111111");
Thread.sleep(5000);
System.out.println("22222222");
}
}
示例二:设置优先级
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
*/
// 创建线程要执行的参数对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread t1 = new Thread(mr,"飞机");
Thread t2 = new Thread(mr,"坦克");
/*System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(Thread.currentThread().getPriority());*/
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
示例三:设置守护线程
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
import com.itheima.a04threadmethod.MyThread;
public class ThreadDemo {
public static void main(String[] args) {
/*
final void setDaemon(boolean on) 设置为守护线程
细节:当其他的非守护线程执行完毕之后,守护线程会陆续结束
*/
MyThread t1 = new MyThread();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
// 把第二个线程设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
示例四:设置礼让线程
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
// 出让当前CPU的执行权
Thread.yield();
}
}
}
import com.itheima.a04threadmethod.MyThread;
public class ThreadDemo {
public static void main(String[] args) {
/*
final static void yield() 出让线程/礼让线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飞机");
t2.setName("坦克");
// 把第二个线程设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
示例五:插入线程
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
import com.itheima.a04threadmethod.MyThread;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
public final void join() 插入线程/插入线程
*/
MyThread t1 = new MyThread();
t1.setName("土豆");
t1.start();
t1.join();
// 执行在main线程当中的
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
四、线程的生命周期
五、线程的安全问题
1. 同步代码块
把操作共享数据的代码锁起来
格式
synchronized(锁) {
操作共享数据的代码
}
特点
①锁默认打开,有一个线程进去了,锁自动关闭;
②里面的代码全部执行完毕,线程出来,锁自动打开。
示例
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。
public class MyThread extends Thread{
// 表示这个类的所有对象,都共享ticket数据
static int ticket = 0;
// 锁对象:一定要唯一
static Object obj = new Object();
@Override
public void run() {
while(true) {
// 同步代码块
synchronized (obj) {
if(ticket < 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第 " + ticket + " 张票!!!");
} else {
break;
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:某电影院目前正在上映国产大片,共有100张票,而它有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();
}
}
2. 同步方法
就是把synchronized关键字加到方法上
格式
修饰符 synchronized 返回值类型 方法名(方法参数){…}
特点
①同步方法是锁住方法里面的所有代码;
②锁对象不能自己指定。
非静态:this
静态:当前类的字节码文件对象
示例
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。用同步方法完成
public class MyRunnable implements Runnable{
int ticket = 0;
@Override
public void run() {
// 1. 循环
while (true) {
// 2. 同步方法
if(method()) break;
}
}
private synchronized boolean method() {
// 3. 判断共享数据是否到了末尾,如果到了末尾
if(ticket == 100) {
return true;
} else {
// 4. 判断共享数据是否到了末尾,如果没有到末尾
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName() + "在卖第 " + ticket + " 张票!!!");
}
return false;
}
}
package com.itheima.a10threadsafe2;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:某电影院目前正在上映国产大片,共有100张票,而它有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();
}
}
3. Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为例更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
示例
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。用JDK5的lock实现
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
// 1. 循环
while(true) {
// 2. 同步代码块
lock.lock();
try {
// 3. 判断
if(ticket == 100) {
break;
} else {
Thread.sleep(10);
ticket++;
System.out.println(getName() + "在卖第 " + ticket + " 张票!!!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
用JDK5的lock实现
*/
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();
}
}
六、死锁
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:死锁
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程A");
t2.setName("线程B");
t1.start();
t2.start();
}
}
public class MyThread extends Thread{
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
// 1. 循环
while (true) {
if("线程A".equals(getName())) {
synchronized (objA) {
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
} else if("线程B".equals(getName())) {
if("线程B".equals(getName())) {
synchronized (objB) {
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (objA) {
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
}
}
七、生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
方法名称 | 说明 |
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
示例一:
public class Desk {
/*
控制生产者和消费者的执行
*/
// 是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
// 总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
public class Cook extends Thread{
@Override
public void run() {
/*
1. 循环
2. 同步代码块
3. 判断共享数据是否到了末尾(到了末尾)
4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while(true) {
synchronized (Desk.lock) {
if(Desk.count == 0) {
break;
} else {
// 判断桌子上是否有食物
if(Desk.foodFlag == 1) {
// 如果有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果没有,就制作食物
System.out.println("厨师做了一碗面条");
// 修改桌子上的食物状态
Desk.foodFlag = 1;
// 等待消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
import sun.security.krb5.internal.crypto.Des;
public class Foodie extends Thread{
@Override
public void run() {
/*
1. 循环
2. 同步代码块
3. 判断共享数据是否到了末尾(到了末尾)
4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while(true) {
synchronized (Desk.lock) {
if(Desk.count == 0) {
break;
} else {
// 先去判断桌子上是否有面条
if(Desk.foodFlag == 0) {
// 如果没有,就等待
try {
Desk.lock.wait(); // 让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 把吃的总数-1
Desk.count--;
// 如果有,就开吃
System.out.println("吃货正在吃面条,还能再吃 " + Desk.count + " 碗!!!");
// 吃完之后,唤醒厨师继续做
Desk.lock.notifyAll();
// 修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:完成生产者和消费者(等待唤醒机制)的代码
实现线程轮流交替执行的效果
*/
// 创建线程对象
Cook c = new Cook();
Foodie f = new Foodie();
// 给线程设置名字
c.setName("厨师");
f.setName("吃货");
// 开启线程
c.start();
f.start();
}
}
示例二:等待唤醒机制(阻塞队列方式实现)
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook( ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断地把面条放到阻塞队列中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
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) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:生产者和消费者必须使用同一个阻塞队列
*/
// 1. 创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 2. 创建线程的对象,并把阻塞队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
// 3. 开启线程
c.start();
f.start();
}
}
八、综合练习
1. 抢红包
抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。其中,红包是共享数据,5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
public class MyThread extends Thread{
// 共享数据
// 100块,分成了3个红包
static BigDecimal money = BigDecimal.valueOf(100.0);
static int count = 3;
// 最小的中奖金额
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(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
prize = BigDecimal.valueOf(r.nextDouble() * bounds);
}
// 设置抽中红包,小数点保留两位,四舍五入
prize = prize.setScale(2, RoundingMode.HALF_UP);
// 从money中减去当前中奖的金额
money = money.subtract(prize);
// 红包数量减1
count--;
System.out.println(getName() + "抢到了" + prize + "元");
}
}
}
}
public class Test {
public static void main(String[] args) {
/*
抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。其中,红包是共享数据,5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
*/
// 创建5个线程对象
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();
}
}
2. 抽奖箱抽奖
有一个抽奖池,该抽奖池存放了奖励的金额,该抽奖池中的奖项为[10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700];
创建两个抽奖箱(线程)设置线程名称为分别为“抽奖箱1”,“抽奖箱2”,随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1又产生了一个10元大奖
抽奖箱1又产生了一个100元大奖
抽奖箱1又产生了一个200元大奖
抽奖箱1又产生了一个800元大奖
抽奖箱1又产生了一个700元大奖
……
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread{
// [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
// 集合:去重
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 + " 元大奖");
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
/*
有一个抽奖池,该抽奖池存放了奖励的金额,该抽奖池中的奖项为[10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700];
创建两个抽奖箱(线程)设置线程名称为分别为“抽奖箱1”,“抽奖箱2”,随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1又产生了一个10元大奖
抽奖箱1又产生了一个100元大奖
抽奖箱1又产生了一个200元大奖
抽奖箱1又产生了一个800元大奖
抽奖箱1又产生了一个700元大奖
*/
// 创建奖池
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();
}
}
3. 多线程统计并求最大值
在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)
在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
分别为:10, 20, 100, 500, 2, 300,最高奖项为300元,总计额932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为:5,50,200,800,80,700,最高奖项为800元,总计额1835元
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class MyThread extends Thread{
// [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
// 集合:去重
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
// 线程一
static ArrayList<Integer> list1 = new ArrayList<>();
// 线程二
static ArrayList<Integer> list2 = new ArrayList<>();
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if(list.isEmpty()) {
if("抽奖箱1".equals(getName())) {
Integer[] arr = new Integer[list1.size()];
arr = list1.toArray(arr);
// 获取数组中的最大值
int max = Collections.max(Arrays.asList(arr));
int sum = 0;
for(int num : list1) {
sum += num;
}
System.out.println("抽奖箱1:" + list1 + ",最高奖项为:" + max + ",总计额为" + sum);
} else {
Integer[] arr = new Integer[list2.size()];
arr = list2.toArray(arr);
// 获取数组中的最大值
int max = Collections.max(Arrays.asList(arr));
int sum = 0;
for(int num : list2) {
sum += num;
}
System.out.println("抽奖箱2:" + list2 + ",最高奖项为:" + max + ",总计额为" + sum);
}
break;
} else {
// 继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
if("抽奖箱1".equals(getName())) {
list1.add(prize);
} else {
list2.add(prize);
}
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
// 创建奖池
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();
}
}
或 线程栈
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread{
// [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
// 集合:去重
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
ArrayList<Integer> boxList = new ArrayList<>();
while (true) {
synchronized (MyThread.class) {
if(list.isEmpty()) {
System.out.println(getName() + boxList);
break;
} else {
// 继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
boxList.add(prize);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
// 创建奖池
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();
}
}
4. 多线程之间的比较
在上一题的基础上继续完成如下需求:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300,最高奖项为300元,总计额932元。
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:5,50,200,800,80,700,最高奖项为800元,总计额1835元。
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元。
以上打印效果只是数据模拟,实际代码运行的效果会有差异。
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
// [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
// 集合:去重
ArrayList<Integer> list;
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> boxList = new ArrayList<>();
while (true) {
synchronized (MyCallable.class) {
if(list.isEmpty()) {
System.out.println(Thread.currentThread().getName() + boxList);
break;
} else {
// 继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
boxList.add(prize);
}
}
Thread.sleep(10);
}
// 把集合中的最大值返回
if(boxList.size() == 0) {
return null;
} else {
return Collections.max(boxList);
}
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
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 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();
// 获取结果
Integer max1 = ft1.get();
Integer max2 = ft2.get();
System.out.println(max1);
System.out.println(max2);
// 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
}
}
九、线程池
1. 核心原理
①创建一个池子,池子中是空的;
②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可;
③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
2. 线程池代码实现
①创建线程池;
②提交任务;
③所有的任务全部执行完毕,关闭线程池。
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称 | 说明 |
public static ExecutorService newCachedThreadPoo() | 创建一个没有上限的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
方法一:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
// 2. 提交任务
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
// 3. 销毁线程池
// poo1.shutdown();
}
}
方法二:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 获取线程池对象
ExecutorService pool1 = Executors.newFixedThreadPool(3);
// 2. 提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
// 3. 销毁线程池
// poo1.shutdown();
}
}
3. 自定义线程池(任务拒绝策略)
不断地提交任务,会有以下三个临界点:
①当核心线程满时,再提交任务就会排队;
②当核心线程满,队伍满时,会创建临时线程;
③当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略。
任务拒绝策略 | 说明 |
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionExeception异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常(这是不推荐的做法) |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
package com.itheima.a01threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
import com.itheima.a01threadpool1.MyRunnable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo1 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数量,能小于0
6, // 最大的线程数量,不能小于0,最大数量 >= 核心线程数量
60, // 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
// 3. 销毁线程池
//pool.shutdown();
}
}
4. 合适的线程池大小
类型 | 线程池大小 |
CPU密集型运算 | 最大并行数 + 1 |
I/O密集型运算 | 最大并行数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间)/ CPU计算时间 |