敏而好学,不耻下问。

一、基本概念

进程:是正在运行的程序

  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
  • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
  • 并发性:任何进程都可以同其他进程一起并发执行

线程:是进程中的单个顺序控制流,是一条执行路径

​ 单线程:一个进程如果只有一条执行路径,则称为单线程程序

​ 多线程:一个进程如果有多条执行路径,则称为多线程程序

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位

多线程的应用场景:

  • 拷贝、迁移大文件
  • 加载大量的资源文件
  • 。。。

并发:在同一时刻,有多个指令在单个cpu交替执行

并行:在同一时刻,有多个指令在多个cpu同时执行

二、实现多线程

实现方式一:继承Thread类

步骤

  1. 自定义一个类继承Thread
  2. 重写run方法
  3. 创建子类的对象,并使用start方法启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}

---------------

public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + ": Hello world");
}
}
}

线程1: Hello world
线程2: Hello world
线程1: Hello world
线程1: Hello world
线程1: Hello world
线程1: Hello world
线程1: Hello world
······
「可以看到输出是两个线程交替进行的」

实现方式二:实现Runnable接口

步骤

  1. 自己定义一个类实现Runnable接口
  2. 重写里面的run方法
  3. 创建自己的类的对象
  4. 创建一个Thread类的对象,并开启线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadDemo {
public static void main(String[] args) {
MyRun r = new MyRun();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);

t1.setName("线程1");
t2.setName("线程2");

t1.start();
t2.start();
}
}

------
public class MyRun implements Runnable{

@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 获取到当前线程的对象,使用静态方法currentThread()
Thread t = Thread.currentThread();
System.out.println(t.getName() + ": hello world");
}
}
}

线程2: hello world
线程1: hello world
线程2: hello world
线程2: hello world
线程2: hello world
线程2: hello world
······
「可以看到输出也是两个线程交替进行的」

实现方式三:利用Callable接口和Future接口

步骤

  1. 创建一个类MyCallable实现Callable接口
  2. 重写call(是有返回值的,表示多线程的运行结果)
  3. 创建MyCallable的对象(表示多线程的要执行的任务)
  4. 创建FutureTask的对象(作用管理多线程的的运行结果)
  5. 创建Thread类的对象,并启动(表示线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.start();
Integer result = ft.get();
System.out.println(result);
}
}

-------

public class MyCallable implements Callable<Integer> {

@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}

5050

image-20240727103143185

常见的成员方法

image-20240727104311793
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package a04threadmethod;

public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread("ucas"); // 注意这里的MyThread是没有仅仅含有String的构造方法的,需要通过super方法重写构造函数
MyThread t2 = new MyThread("nssc");

t1.start();
t2.start();

// 获取线程执行到这个方法,此时获取的就是哪条线程的对象
// Thread t = Thread.currentThread();
// System.out.pr/intln(t.getName());
// 输出: main

// System.out.println("当前线程即将进入5s的睡眠");
// Thread.sleep(5000);
// System.out.println("休眠完毕,线程被唤醒");

}
}

-------

package a04threadmethod;

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);
}
}
}

抢占式调度,在jvm中,线程的优先级从1-10,默认为5,优先级越高,被选中执行的概率越大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr, "ucas");

Thread t2 = new Thread(mr, "nssc");

// System.out.println(t1.getPriority()); // 5
// System.out.println(t2.getPriority()); // 5
// System.out.println(Thread.currentThread().getPriority()); // 5

t1.setPriority(1);
t2.setPriority(10);

t1.start();
t2.start();
}
}

-------

public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}

守护线程:当其他的非守护线程执行完毕之后,守护线程会陆续结束

应用场景:在qq聊天界面中,聊天消息接发为线程1,文件传输中为线程2,当关闭聊天窗口时,如果线程2为守护线程,那么即使聊天窗口关闭(线程1结束),线程2仍会执行一直到完毕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}

------

public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}

------

public class ThreadDemo {
public static void main(String[] args) {
/*
当其他的非守护线程执行完毕之后,守护线程会陆续结束
*/
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
MyThread2 t3 = new MyThread2();

t1.setName("女神1");
t2.setName("女神2");
t3.setName("备胎");

// 将t3设置为守护线程(备胎线程)
t3.setDaemon(true);

t1.start();
t2.start();
t3.start();
}
}

线程的生命周期

image-20240727142013196

Q:sleep方法会让线程睡眠,睡眠时间到了之后,立马就会执行该线程的代码吗?

A:不会,此时线程会进入就绪状态,需要抢夺刀cpu的执行权后才能执行

三、线程安全问题

需求:某电影院正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MyThread extends Thread{

// 注意,如果不加static关键字,那么每个窗口卖的票都是不共享的,即每个窗口都卖了100张票
static int ticket = 0; // 0 ~ 99

@Override
public void run() {
while(true){
if(ticket < 100){
ticket ++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} else {
break;
}
}
}
}

------

public class ThreadDemo {
public static void main(String[] args) {
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正在卖第96张票!!!
窗口2正在卖第97张票!!!
窗口3正在卖第94张票!!!
窗口3正在卖第99张票!!!
窗口3正在卖第100张票!!!
窗口1正在卖第90张票!!!
窗口2正在卖第99张票!!!

我们可以看出这样是有问题的,同一张票被两个窗口卖出过,于是我们就需要对共享数据上锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyThread extends Thread{

static int ticket = 0; // 0 ~ 99

// 锁对象要保证唯一,即要加static关键字
static Object obj = new Object();

@Override
public void run() {
while(true){
// 同步代码块
synchronized(obj){
if(ticket < 100){
ticket ++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} else {
break;
}
}
}
}
}

注意:

  1. synchronized的地方要写对,什么时候上锁是有讲究的,例如如果上面的例子,锁上在while外面的话,结果将是窗口1卖完所有的100张票就直接结束了
  2. 锁对象可以任意,但是一定要保持唯一,这是因为,要保证所有线程看到的都是同一把锁

同步方法:将synchronized关键字加到方法上

特点1:同步方法是锁住方法里面的所有代码

特点2:锁对象不能自己指定 如果是非静态方法,锁对象为this,如果是静态方法,锁对象为当前类的文件对象

对上述需求使用同步方法完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package a05threadsafe1;

public class MyThread extends Thread{

static int ticket = 0; // 0 ~ 99

// 锁对象要保证唯一,即要加static关键字
static Object obj = new Object();

@Override
public void run() {
while(true){
// 同步代码块
synchronized(obj){
try {
if (method()) break;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

private boolean method() throws InterruptedException {
if(ticket < 100){
Thread.sleep(100);
ticket ++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} else {
return true;
}
return false;
}
}

四、Lock锁

Lock中提供了获得锁和释放锁的方法

获得锁:void lock()

释放锁:void unlock()

由于Lock是接口,不能直接实例化,于是采用它的实现类ReentrantLock来实例化

1
2
3
4
5
6
7
8
9
10
static Lock lock = new ReentrantLock();

lock.lock();
try {
// 需要上锁的代码块
} catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}

五、死锁

  • 概述

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

  • 什么情况下会产生死锁

    1. 资源有限
    2. 同步嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();

new Thread(()->{
while(true){
synchronized (objA){
//线程一
synchronized (objB){
System.out.println("小康同学正在走路");
}
}
}
}).start();

new Thread(()->{
while(true){
synchronized (objB){
//线程二
synchronized (objA){
System.out.println("小薇同学正在走路");
}
}
}
}).start();
}
}

六、生产者和消费者(等待唤醒机制)

方法名称 说明
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程

image-20240729095610337

需求:完成生产者和消费者(等待唤醒机制)的代码,实现线程轮流交替执行的结果

ThreadDemo.java

1
2
3
4
5
6
7
8
public class ThreadDemo {
public static void main(String[] args) {
Foodie foodie = new Foodie();
Cook cook = new Cook();
foodie.start();
cook.start();
}
}

Desk.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Desk {
// 作用:控制生产者和消费者的执行

// 桌子上是否有面条 0:没有苗条 1:有面条
public static int foodFlag = 0;

// 总个数
public static int count = 10;

// 锁对象
public static Object lock = new Object();
}

Foodie.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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) {
throw new RuntimeException(e);
}
} else { // 有面条,开始吃
Desk.count --;
System.out.println("正在吃面条,还能吃" + Desk.count + "碗!!!");
Desk.lock.notify(); // 唤醒cook继续做
Desk.foodFlag = 0;
}
}
}
}
}
}

Cook.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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) {
throw new RuntimeException(e);
}
} else { // 没有面条,开始做
System.out.println("厨师做了一碗面条");
Desk.lock.notify();
Desk.foodFlag = 1;
}
}
}
}
}
}

厨师做了一碗面条
正在吃面条,还能吃9碗!!!
厨师做了一碗面条
正在吃面条,还能吃8碗!!!
厨师做了一碗面条
正在吃面条,还能吃7碗!!!
厨师做了一碗面条
正在吃面条,还能吃6碗!!!
厨师做了一碗面条
正在吃面条,还能吃5碗!!!
厨师做了一碗面条
正在吃面条,还能吃4碗!!!
厨师做了一碗面条
正在吃面条,还能吃3碗!!!
厨师做了一碗面条
正在吃面条,还能吃2碗!!!
厨师做了一碗面条
正在吃面条,还能吃1碗!!!
厨师做了一碗面条
正在吃面条,还能吃0碗!!!

使用阻塞队列实现

image-20240729102529588

阻塞队列底层就是用锁实现的,于是不用开发者手动加锁

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);

c.start();
f.start();

}
}

Foodie.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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) {
throw new RuntimeException(e);
}
}

}
}

Cook.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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) {
throw new RuntimeException(e);
}
}
}
}

线程的六大状态

新建状态(NEW) -------> 创建线程对象

就绪状态(RUNNABLE) ------> start方法

阻塞状态 (BLOCKED) ------> 无法获得锁对象

等待状态(WAITING) ------> wait方法

计时等待(TIMED_WAITING) ------> sleep方法

结束状态(TERMINATED) ------> 全部代码运行完毕

七、多线程练习题

练习01

需求:一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,
要求:请用多线程模拟卖票过程并打印剩余电影票的数量

MyThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyThread extends Thread{
public static int count = 1000;
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if(count == 0){
break;
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + "卖出一张票");
count --;
System.out.println("当前剩余票数:" + count + "张");
}
}
}

}
}

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ThreadDemo {
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();


}
}

练习02

需求:有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多 线程模拟该过程并将线程的名字和礼物的剩余数量打印出来.

MyRun.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyRun implements Runnable{

public int count = 100; // 这里MyRun类只创建了一次,不用加static关键字

@Override
public void run() {
while (true){
synchronized (MyRun.class) {
if(count <= 10) break;
else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count --;
System.out.println(Thread.currentThread().getName() + "正在发送礼品... 此时礼品剩余数量" + count);
}
}
}
}
}

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import a08practice01.MyThread;

public class ThreadDemo {
public static void main(String[] args) {
/*
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,
利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来.
*/
MyRun mr = new MyRun();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);

t1.setName("窗口1");
t2.setName("窗口2");

t1.start();
t2.start();

}
}

练习03

需求:同时开启两个线程,共同获取1-100之间的所有数字。要求:将输出所有的奇数。

MyRun.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyRun implements Runnable{

public int num = 100;

@Override
public void run() {
while (true){
synchronized (MyRun.class){
if(num < 1) break;
else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(num % 2 == 1) System.out.println(Thread.currentThread().getName() + "@ " + num);
num --;
}
}
}
}
}

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package a08practice03;

public class ThreadDemo {
public static void main(String[] args) {
/*
同时开启两个线程,共同获取1-100之间的所有数字。
要求:将输出所有的奇数。
*/
MyRun mr = new MyRun();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}

练习04

抢红包

1
2
3
4
5
6
7
8
9
10
11
需求:
微信中的抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。
其中,红包是共享数据。
5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t1.setName("苏无名");
t2.setName("樱桃");
t3.setName("卢凌风");
t4.setName("裴喜君");
t5.setName("费鸡师");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}

MyThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Random;

public class MyThread extends Thread{

static int money = 10000;
static int num = 3;
static final int MIN = 1;

@Override
public void run() {
synchronized (MyThread.class){
if(num <= 0) {
System.out.println("很遗憾~" + getName() + "没有抢到红包~");
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
int prize = 0;
if(num == 1) prize = money;
else {
Random r = new Random();
int bounds = money - (num - 1) * MIN;
prize = r.nextInt(bounds);
if(prize < MIN) prize = MIN;
}
money -= prize;
num --;
System.out.println("恭喜 " + getName() + " 抢到红包" + prize / 100.0 + "元!!!");
}
}
}
}

练习05

抽奖箱抽奖

1
2
3
4
5
6
7
8
9
10
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
.....

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import a08practice05.MyThread;

import java.util.ArrayList;
import java.util.Collections;

public class ThreadDemo {
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();
}
}

MyThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.ArrayList;
import java.util.Collections;

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.size() == 0) {
break;
} else {
Collections.shuffle(list);
int prize = list.remove(0);
System.out.println(getName() + " 又产生了一个 " + prize + " 元大奖!!!");
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

}
}

练习06

多线程统计并求最大值

1
2
3
4
5
6
7
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽的过程中,不打印,抽完时一次性打印(随机) 在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元

ThreadDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.ArrayList;
import java.util.Collections;

public class ThreadDemo {
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();
}
}

MyThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.ArrayList;
import java.util.Collections;

public class MyThread extends Thread{

ArrayList<Integer> list;

public MyThread(ArrayList<Integer> list) {
this.list = list;
}

@Override
public void run() {
ArrayList<Integer> boxlist = new ArrayList<>();
while(true){
synchronized (a08practice05.MyThread.class){
if(list.size() == 0) {
boxlist.sort(Integer::compareTo);
int MAX = boxlist.get(boxlist.size() - 1);
int sum = boxlist.stream().mapToInt(Integer::intValue).sum();
System.out.println("抽奖箱1"+ boxlist + " 最高金额 " + MAX + " 总金额 " + sum);
break;
} else {
Collections.shuffle(list);
int prize = list.remove(0);
boxlist.add(prize);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

}
}

八、线程池

线程池主要核心原理:

  1. 创建一个池子,池子是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次在提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等候

线程池实现步骤

  1. 创建线程池

    Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

    方法名称 说明
    public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
    public static ExecutorService new FixedThreadPool(int nThreads) 创建有上限的线程池
  2. 提交任务

  3. 所有的任务全部执行完毕,关闭线程池

九、自定义线程池

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

参数一:核心线程数量              不能小于0
参数二:最大线程数                不能小于0,最大数量 >= 核心线程数量
参数三:空闲线程最大存活时间       不能小于0
参数四:时间单位                  用TimeUnit指定
参数五:任务队列                  不能为null
参数六:创建线程工厂              不能为null
参数七:任务的拒绝策略             不能为null
1
2
3
4
5
6
7
8
9
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //核心线程数量,能小于0
6, //最大线程数,不能小于0,最大数量 >= 核心线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);

image-20240730110520977

不断的提交任务,会有三个临界点:

  1. 当核心线程满时,在提交任务就会排队
  2. 当核心线程满,队伍满时,会创建临时线程
  3. 当核心线程满,队伍满时,临时线程满时,会触发任务拒绝策略