多线程(十九)


多线程(十九)

进程:进程是程序的基本执行实体。

线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中实际运作单位,简单理解为在应用软件中互相独立,可以同时运行的功能。

有了多线程,可让程序同时做多件事情,多线程可以提高效率,若想让多个事情同时运行就需要用到多线程,比如:软件中的耗时操作,所有的聊天软件,所有的服务器。

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

并行:在同一时刻,有多个指令在单个CPU上同时执行。

线程的调度

  1. 抢占式调度:指优先让可运行池中优先级高的线程占用CPU,优先级并不绝对,只不过优先级越高,占用CPU的概率越高。
  2. 分时调度:又称非抢占式调度,指让所有线程轮流获得cpu的使用权。

多线程的实现方式

线程是程序中的执行线程,java虚拟机允许应用程序并发地运行多个执行线程。

  1. 继承Thread类的方式进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用Callable接口和Future接口方式实现

继承Thread类

步骤

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建子类对象,并启动线程(利用start方法启动,run方法会自动调用)

范例:

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
//Test.java
public class Test {
public static void main(String[] args) {
Function f1=new Function();
Function f2=new Function();
f1.setName("线程1");
f2.setName("线程2");
f1.start();
f2.start();
}
}
//Function.java
public class Function extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"执行");
}
}
}
/*
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程2执行
线程1执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
*/

实现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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//Test.java
public class Test {
public static void main(String[] args) {
//创建function对象,表示多线程要执行的任务
Function f=new Function();
//创建线程对象
Thread t1=new Thread(f);
Thread t2=new Thread(f);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
//Function.java
public class Function implements Runnable {
//线程要执行的代码
@Override
public void run() {
//获取当前线程的对象
Thread t=Thread.currentThread();
for (int i=0;i<10;i++) {
System.out.println(t.getName()+"执行");
}
}
}
/*
线程1执行
线程1执行
线程2执行
线程1执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
*/

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//MyCallable.java

import java.util.concurrent.Callable;

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

//

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个类MyCallable实现Callable接口
MyCallable mc=new MyCallable();
//创建FutureTask的对象(作用为管理多线程运行的结果)
FutureTask<Integer> ft=new FutureTask<>(mc);
//创建线程的对象
Thread t=new Thread(ft);
//启动线程
t.start();
//获取多线程运行的结果
Integer integer = ft.get();
System.out.println(integer);

}
}
/*
5050
*/

三种方式对比

  • 继承Thread类

    • 优点:编程比较简单,可直接使用Thread类的方法
    • 缺点:可扩展性差,不能再继承其他类
  • 实现Runnable接口

    • 优点:扩展性强,实现该接口的同时还可以继承其他类
    • 缺点:编程相对复杂,不能直接使用Thread类中的方法
  • 实现Callable接口

    • 优点:扩展性强,实现该接口的同时还可以继承其他类,可获取多线程运行的结果
    • 缺点:编程相对复杂,不能直接使用Thread类中的方法

常见成员方法

getName

String getName()

说明:返回此线程的名称,若没有给线程设置名字,线程也是有默认名字的,默认名字格式:Thread-X(X序号,从0开始)

setName

void setName(String name)

说明:设置线程名字(构造方法也可以设置名字)

currentThread

static Thread currentThread()

说明:获取当前线程的对象,当虚拟机启动后,会自动启用多条线程,其中一条叫做main线程,它的作用便是调用main方法,并执行其中代码。

sleep

static void sleep(long time)

说明:让线程休眠指定的时间,单位为毫秒(1秒等于1000毫秒),哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间。

setPriority

setPriority(int newPriority)

说明:设置线程的优先级,优先级最高位10,最低为1,默认为5

getPriority

final int getPriority()

说明:获取线程的优先级

setDaemon

final void setDaemon(boolean on)

说明:设置守护线程,当其他非守护线程执行完毕后,守护线程会陆续结束。

yield

public static void yield()

说明:出让线程/礼让线程

join

public final void join()

说明:插入线程/插队线程。表示把调用该方法的线程插入到当前线程之前

范例

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
//MyThread.java
public class MyThread extends Thread {
public MyThread() {
}

public MyThread(String name) {
super(name);
}

@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName());
}
}
//Test.java
public class Test {
public static void main(String[] args){
//getName默认
MyThread mt1=new MyThread();
mt1.start();
//构造方法设置
MyThread mt2=new MyThread("线程2");
mt2.start();
//通过setName设置后的getName
MyThread mt3=new MyThread();
mt3.setName("线程3");
mt3.start();

//currentThread
Thread t=Thread.currentThread();
System.out.println(t.getName());
}
}
/*
main
线程2
Thread-0
线程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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//MyThread.java
public class MyThread implements Runnable {
@Override
public void run() {
for (int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
//Test.java
public class Test {
public static void main(String[] args){
MyThread mt=new MyThread();
Thread t1=new Thread(mt,"线程1");
Thread t2=new Thread(mt,"线程2");
System.out.println("默认");
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println("修该后");
t1.setPriority(1);
t2.setPriority(10);
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
t1.start();
t2.start();
}
}
/*
默认
5
5
修该后
1
10
线程2:0
线程2:1
线程1:0
线程2:2
线程1:1
线程1:2
线程2:3
线程1:3
线程2:4
线程1:4
线程2:5
线程2:6
线程2:7
线程2:8
线程2:9
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
*/
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
49
50
//Test.java
public class Test {
public static void main(String[] args){
MyThread1 mt1=new MyThread1();
MyThread2 mt2=new MyThread2();
mt1.setName("守护线程");
mt2.setName("非守护线程");
mt1.setDaemon(true);
mt1.start();
mt2.start();
}
}
//MyThread1.java
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+i);
}
}
}
//MyThread2.java
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
}
}
}
/*
守护线程0
非守护线程0
非守护线程1
守护线程1
非守护线程2
守护线程2
非守护线程3
非守护线程4
非守护线程5
非守护线程6
非守护线程7
非守护线程8
非守护线程9
守护线程3
守护线程4
守护线程5
守护线程6
守护线程7
*/
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
//Test.java
public class Test {
public static void main(String[] args){
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.setName("线程一");
mt2.setName("线程二");
mt1.start();
mt2.start();
}
}
//MyThread.java
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
Thread.yield();
}
}
}
/*
线程一0
线程二0
线程二1
线程一1
线程一2
线程一3
线程二2
线程一4
线程一5
线程二3
线程二4
线程二5
线程一6
线程二6
线程一7
线程二7
线程一8
线程一9
线程二8
线程二9
*/
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
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt=new MyThread();
mt.setName("线程一");
mt.start();
mt.join();

for (int i = 0; i < 10; i++) {
System.out.println("main线程"+i);
}
}
}
//MyThread.java
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
Thread.yield();
}
}
}
/*
线程一0
线程一1
线程一2
线程一3
线程一4
线程一5
线程一6
线程一7
线程一8
线程一9
main线程0
main线程1
main线程2
main线程3
main线程4
main线程5
main线程6
main线程7
main线程8
main线程9
*/

线程的生命周期

新建状态NEW:创建线程对象。调用start方法后变成就绪状态

就绪状态RUNNABLE:有执行资格,没执行权。不停的抢CPU

阻塞状态BLOCKED:无法获得锁对象

等待状态WAITING:wait方法

计时状态TIMED_WAITING:sleep方法

死亡状态TERMINATED:若run执行完毕,线程死亡,变成垃圾

同步代码块

把操作共享数据的代码锁起来。

特点

  1. 锁默认打开,有一个线程进去,缩自动关闭
  2. 里面的代码全部执行完毕,线程出来,缩自动打开

格式

1
2
3
4
synchronized(锁){
操作共享数据的代码
}
//锁对象一定要是唯一的,一般写当前类的字节码文件。

范例

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
49
50
51
52
53
54
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt1=new MyThread();
mt1.setName("窗口1");

MyThread mt2=new MyThread();
mt2.setName("窗口2");

MyThread mt3=new MyThread();
mt3.setName("窗口3");

mt1.start();
mt2.start();
mt3.start();

}
}
//MyThread.java
public class MyThread extends Thread{
static int ticketNum=1;
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (ticketNum <=10) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "卖出第" + ticketNum + "张票");
ticketNum++;

} else {
break;
}
}
}
}
}
/*
窗口1卖出第1张票
窗口1卖出第2张票
窗口3卖出第3张票
窗口3卖出第4张票
窗口3卖出第5张票
窗口3卖出第6张票
窗口3卖出第7张票
窗口3卖出第8张票
窗口2卖出第9张票
窗口2卖出第10张票
*/

同步方法

就是把synchronized关键字加到方法上。

格式

修饰符 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票,同步方法实现
//Test.java
1public class Test {
public static void main(String[] args){
MyThread myThread=new MyThread();
Thread t1=new Thread(myThread);
Thread t2=new Thread(myThread);
Thread t3=new Thread(myThread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//MyThread.java
public class MyThread implements Runnable{
int ticketNum=0;
@Override
public void run() {
while (true){
if (method()){
break;
}
}
}
private synchronized boolean method(){
if(ticketNum==10){
return true;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum++;
System.out.println(Thread.currentThread().getName()+ "卖出第" + ticketNum + "张票");
}
return false;
}
}
/*
窗口1卖出第1张票
窗口1卖出第2张票
窗口1卖出第3张票
窗口1卖出第4张票
窗口1卖出第5张票
窗口1卖出第6张票
窗口1卖出第7张票
窗口1卖出第8张票
窗口3卖出第9张票
窗口2卖出第10张票
*/

lock锁

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

void lock(); 获得锁

void unlock(); 释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

ReentrantLock的构造方法:

ReentrantLock(); 创建一个ReentrantLock的实例。

范例

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
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票,Lock实现
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt1=new MyThread();
mt1.setName("窗口1");

MyThread mt2=new MyThread();
mt2.setName("窗口2");

MyThread mt3=new MyThread();
mt3.setName("窗口3");

mt1.start();
mt2.start();
mt3.start();

}
}
//MyThread.java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread{
static int ticketNum=1;
static Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (ticketNum <=100) {
Thread.sleep(10);
System.out.println(getName() + "卖出第" + ticketNum + "张票");
ticketNum++;
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}
}
}

:写锁的时候不要让锁嵌套,否则可能会死锁。

等待唤醒机制

方式一

等待唤醒机制(生产者消费者模式)是多个线程之间的一种协作机制。

思路:

此处将消费者比喻成吃货,生产者比喻成厨师。

  • 消费者:消费数据
    1. 判断桌子上是否有食物
    2. 若无,等待
    3. 若有,开吃
    4. 吃完后,唤醒厨师继续做
  • 生产者:生产数据
    1. 判断桌子上是否有食物
    2. 若有,等待
    3. 若无,做饭
    4. 把食物放到桌子上
    5. 唤醒等待的消费者开吃

常见方法

wait

void wait()

说明:当前线程等待,直到被其他线程唤醒

notify

void notify()

说明:随机唤醒单个线程

notifyAll

void notifyAll()

说明:唤醒所有线程

范例

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//Desk.java
public class Desk {
public static int count=10;//吃货能吃的饭的数量
public static Object lock=new Object();//锁
public static int state=0;//桌子的状态,若无食物为0,若有食物为1
}
//Cookie.java
public class Cookie extends Thread {
/*
1. 循环
2.同步代码块
3.判断共享数据是否到了尽头(到了尽头)
4.判断共享数据是否到了尽头(没到尽头,执行核心逻辑)
*/
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else {//判断桌子上是否有食物
if(Desk.state==0){//无食物,厨师做饭
System.out.println("厨师做饭");
//桌子上有了食物,状态设为1
Desk.state=1;
//唤醒吃货
Desk.lock.notifyAll();
}else{//若有,则等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
//Foodie.java
public class Foodie extends Thread{
/*
1. 循环
2.同步代码块
3.判断共享数据是否到了尽头(到了尽头)
4.判断共享数据是否到了尽头(没到尽头,执行核心逻辑)
*/
@Override
public void run() {
while (true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else {
//判断桌子是否有食物
if(Desk.state==0){
try {//若无,等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{//若有,开吃
//先将饭量-1
Desk.count--;
System.out.println("吃货还能吃"+Desk.count+"顿饭");
//吃饭后,桌子上没有事物,状态设为0
Desk.state=0;
//唤醒厨师
Desk.lock.notifyAll();
}
}
}
}
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Cookie cookie=new Cookie();
Foodie foodie=new Foodie();
cookie.start();
foodie.start();
}
}
/*
厨师做饭
吃货还能吃9顿饭
厨师做饭
吃货还能吃8顿饭
厨师做饭
吃货还能吃7顿饭
厨师做饭
吃货还能吃6顿饭
厨师做饭
吃货还能吃5顿饭
厨师做饭
吃货还能吃4顿饭
厨师做饭
吃货还能吃3顿饭
厨师做饭
吃货还能吃2顿饭
厨师做饭
吃货还能吃1顿饭
厨师做饭
吃货还能吃0顿饭
*/

方式二

阻塞队列实现等待唤醒机制。

阻塞队列继承结构

阻塞队列实现Iterable,Collection,Queue,BlockingQueue接口,有两个实现类:

  1. ArrayBlockingQueue:底层是数组,有界
  2. LinkedBlockingQueue:底层是链表,无界,但不是真的无界,最大为int最大值

思路

此处将消费者比喻成吃货,生产者比喻成厨师。

  • 厨师
    1. 构造方法中接收一个阻塞队列对象
    2. run方法中循环向阻塞队列添加食物
    3. 打印添加结果
  • 吃货
    1. 构造方法中接收一个阻塞队列对象
    2. run方法中循环获取阻塞队列的食物
    3. 打印获取结果

核心方法

put(anObject)

将参数放入队列,若放不进去会堵塞。

take()

取出第一个数据,取不到会堵塞。

:put,take方法里本身就有锁

范例

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
49
50
51
52
53
54
55
//Test.java
import java.util.concurrent.ArrayBlockingQueue;

public class Test {
public static void main(String[] args){
ArrayBlockingQueue<String> abq=new ArrayBlockingQueue<>(1);//创建阻塞对象,并设置容量
Cookie cookie=new Cookie(abq);
Foodie foodie=new Foodie(abq);
cookie.start();
foodie.start();

}
}
//Cookie.java
import java.util.concurrent.ArrayBlockingQueue;

public class Cookie extends Thread{
ArrayBlockingQueue<String> abq;
public Cookie(ArrayBlockingQueue<String> abq){
this.abq=abq;
}

@Override
public void run() {
while (true){
try {
abq.put("食物");
System.out.println("厨师放食物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//Foodie.java
import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread{
ArrayBlockingQueue<String> abq;
public Foodie(ArrayBlockingQueue<String> abq){
this.abq=abq;
}
@Override
public void run() {
while (true){
try {
String s=abq.take();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

练习

例1

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*
假设:100元分成了三个红包(均为整数),有5人去抢
其中红包是共享数据。
5个人5条线程
打印结果如下:
XXX抢到了XX元
XXX抢到了XX元
XXX抢到了XX元
XXX没抢到
XXX没抢到
*/
//Test.java
public class Test {
public static void main(String[] args) {
RedPackage rp1=new RedPackage();
RedPackage rp2=new RedPackage();
RedPackage rp3=new RedPackage();
RedPackage rp4=new RedPackage();
RedPackage rp5=new RedPackage();

rp1.setName("线程1");
rp2.setName("线程2");
rp3.setName("线程3");
rp4.setName("线程4");
rp5.setName("线程5");

rp1.start();
rp2.start();
rp3.start();
rp4.start();
rp5.start();
}
}
//RedPackage.java
import java.util.Random;

public class RedPackage extends Thread{
static int sum=100;
static int count=3;
@Override
public void run() {
synchronized (RedPackage.class){
if(count==0){
System.out.println(getName()+"没抢到");
}else{
if(count==1){
int money=sum;
System.out.println(getName()+"抢到了"+money+"元");
}else{
Random r=new Random();
int money=r.nextInt(sum-(count-1))+1;
System.out.println(getName()+"抢到了"+money+"元");
sum=sum-money;
}
count--;
}
}
}
}
/*
线程1抢到了3元
线程5抢到了40元
线程4抢到了57元
线程3没抢到
线程2没抢到
*/

例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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为10,5,20,50,100,200,500,800,2,80,300,700
创建两个抽奖箱(线程)设置线程名分别为“抽奖箱1”和“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
在此次抽奖过程中,抽奖箱2共产生6个奖项
分别为:800,10,80,500,700,2最高奖项为800元,总计额为2092元
在此次抽奖过程中,抽奖箱1共产生6个奖项
分别为:5,100,50,300,20,200最高奖项为300元,总计额为675元
在此次抽奖过程中,抽奖箱2中产生最大奖项,该奖项金额为800元
*/
//Lottery.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;

public class Lottery implements Callable<Integer> {
ArrayList<Integer> moneyBox;
public Lottery(ArrayList<Integer> moneyBox){
this.moneyBox=moneyBox;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> list=new ArrayList<>();
int sum=0;
while (true){
synchronized (Lottery.class) {
if (moneyBox.size() == 0) {
print(list,Collections.max(list),sum);
break;
} else {
Collections.shuffle(moneyBox);
int money = moneyBox.remove(0);
list.add(money);
sum+=money;
}
}
Thread.sleep(10);
}
if(list.size()==0){
return null;
}
return Collections.max(list);
}
private static void print(ArrayList<Integer> list,int max,int sum){
System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()+"共产生"+list.size()+"个奖项");
System.out.print("分别为:");
for(int i=0;i<list.size();i++){
System.out.print(list.get(i));
if(i< list.size()-1){
System.out.print(",");
}
}
System.out.println("最高奖项为"+max+"元,总计额为"+sum+"元");
}
}
//Test.java
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> moneyBox=new ArrayList<>();
Collections.addAll(moneyBox,10,5,20,50,100,200,500,800,2,80,300,700);
Lottery lottery=new Lottery(moneyBox);
FutureTask<Integer> ft1=new FutureTask<>(lottery);
FutureTask<Integer> ft2=new FutureTask<>(lottery);

Thread t1=new Thread(ft1);
Thread t2=new Thread(ft2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
int money;
String name;
if(ft1.get()>ft2.get()){
money=ft1.get();
name=t1.getName();
}else{
money=ft2.get();
name=t2.getName();
}
System.out.println("在此次抽奖过程中,"+name+"产生最大值,最大值金额为"+money);

}
}
/*
在此次抽奖过程中,抽奖箱1共产生6个奖项
分别为:80,100,50,5,10,700最高奖项为700元,总计额为945元
在此次抽奖过程中,抽奖箱2共产生6个奖项
分别为:500,200,20,300,2,800最高奖项为800元,总计额为1822元
在此次抽奖过程中,抽奖箱2产生最大值,最大值金额为800

*/

线程池

核心原理

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

代码实现步骤

  1. 创建线程池
  2. 提交任务
  3. 所有的任务全部执行完毕,关闭线程池

Executors

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

newCachedThreadPool

格式

public static ExecutorsService newCachedThreadPool()

说明:创建一个几乎没有上限的线程池

newFixedThreadPool

格式

public static ExecutorsService newFixedThreadPool(int nThreads)

说明:创建一个有上限的线程池

范例

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
//Test.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
public static void main(String[] args) throws InterruptedException {
//1.获取线程池对象
ExecutorService pool= Executors.newCachedThreadPool();
//2.提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//3.销毁线程池
pool.shutdown();
}
}
//MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
/*
pool-1-thread-1
pool-1-thread-3
pool-1-thread-5
pool-1-thread-2
pool-1-thread-4
*/
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
//MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
//1.获取线程池对象
ExecutorService pool= Executors.newFixedThreadPool(3);
//2.提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//3.销毁线程池
pool.shutdown();
}
}
/*
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-1
pool-1-thread-2
*/

自定义线程

步骤

  1. 创建一个空的池子

  2. 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程。

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

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

核心元素(参数)

  1. 核心线程数量(不少于0)
  2. 线程池中最大线程数量(最大数量>=核心线程数量)
  3. 空闲时间(值)(不能小于0)
  4. 空闲时间(单位)(用TimeUnit绑定)
  5. 阻塞队列(不能为null)
  6. 创建线程工厂(不能为null)
  7. 要执行的任务过多时的解决方案(不能为null)

范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test {
public static void main(String[] args){
ThreadPoolExecutor pool=new ThreadPoolExecutor(
3,//核心线程数量(不少于0)
6,//线程池中最大线程数量(最大数量>=核心线程数量)
60,//空闲时间(值)(不能小于0)
TimeUnit.SECONDS,//空闲时间(单位)(用TimeUnit绑定)
new ArrayBlockingQueue<>(3),//阻塞队列(不能为null)
Executors.defaultThreadFactory(),//创建线程工厂(不能为null)
new ThreadPoolExecutor.AbortPolicy()//要执行的任务过多时的解决方案(不能为null)
);
}
}

线程池多大合适

最大并行数:可用Runtime.getRuntime().availableProcessors()返回当前电脑的最大并行数

1
2
3
CPU密集型运算:即项目中CPU计算居多,线程池大小=最大并行数+1  

I/O密集型运算:即项目中文件操作居多,线程池大小=最大并行数*期望CPU利用率*(总时间(CPU计算时间+等待时间)/CPU计算时间)

Author: ljs
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source ljs !
评论
  TOC