Thread类常用方法

Thread类

补充上面多线程的方法

Thread类的构造方法

image-20230529090429456

Thread类常用方法

image-20230529090446658

参考代码

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 com.itheima.demo01_Thread;
/*
练习Thread类的常用方法
*/
public class MyThread {
public static void main(String[] args) throws InterruptedException {
// 用lambda的形式提前准备一个任务对象
Runnable r = ()->{
// 获取线程的名称,必须先有线程的对象,通过线程对象,获取线程名称
// 1: 类名调用静态方法,就可以获取线程对象,利用线程对象,获取线程名称
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("lambda的方式获取了线程名:"+name);
}
};

// 1: 采用runnable的方式实现多线程代码
Thread t=new Thread(r,"线程小强");
t.start();

new Thread(r,"线程旺财").start();

// 让main线程在这里,等待上面的小强线程
t.join();

System.out.println("mian方法结束了...");
}
}

多线程安全问题

  • 概述

    • 多线程安全就是当多个线程操作共享资源的时候,出现的逻辑错误,就是安全问题;我们应该通过技术手动避免这种问题的发生!
  • 产生原因

    • 有多线程;
    • 有共享数据;
    • 有数据修改;
  • 解决的思路

    • 让多个线程”排队操作共享数据”即可;
  • 取款案例思路分析

image-20230529102221947

取款案例代码实现(出现安全问题)

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
/*
取款的账户类
*/
public class Account {
private double money;

public Account(double money) {
this.money = money;
}

public double getMoney() {
return money;
}

public void setMoney(double money) {
this.money = money;
}


}

/*
取款的任务类
*/
public class MyRun implements Runnable{
// 为了让run方法中,可以操作 Account对象,所以需要设计一个成员变量,并要求通过构造方法给该成员变量存值
private Account a;

public MyRun(Account a) {
this.a = a;
}

@Override
public void run() {
// 操作Account对象中的余额
// 获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+"即将开始判断账户余额");
if(a.getMoney() > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额
a.setMoney(a.getMoney()-1000);
// 有余额,可以取款
// 提示剩余的余额
System.out.println(name+"取出了"+1000+"元钱!,剩余:"+a.getMoney());
}else {
// 没有余额,取款失败
System.out.println(name+"取款失败!");
}
}
}

/*
测试类,测试
*/
public class Test {
public static void main(String[] args) {
// 1: 创建资源对象
Account a = new Account(1000);
// 2: 创建两个线程,都去执行取款的任务
MyRun my = new MyRun(a);
// 3: 创建两个线程,执行任务
new Thread(my,"小红").start();
new Thread(my,"小华").start();
}
}

取款案例的安全问题分析

image-20230529104207121

同步代码块

  • 概述
    • 通过关键字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
/*
取款的任务类
*/
public class MyRun implements Runnable{
// 为了让run方法中,可以操作 Account对象,所以需要设计一个成员变量,并要求通过构造方法给该成员变量存值
private Account a;

public MyRun(Account a) {
this.a = a;
}
public void run() {
// 操作Account对象中的余额
// 获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+"即将开始判断账户余额");
// 把可能出问题的代码,使用同步代码块,包裹起来即可
synchronized (a){
System.out.println(name+"已经进入同步代码块...");
if(a.getMoney() > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额
a.setMoney(a.getMoney()-1000);
// 有余额,可以取款
// 提示剩余的余额
System.out.println(name+"取出了"+1000+
"元钱!,剩余:"+a.getMoney());
}else {
// 没有余额,取款失败
System.out.println(name+"取款失败!");
}
}
}
}
// 其他两个类不变

注意事项

  1. 一般使用资源对象当成锁对象;
  2. 必须保证锁对象要唯一,否则无效!

同步方法

  • 概述
    • 当同步代码块中所有的代码刚好在某个方法内完成的时候,为了增强代码的可读性,可以把整个方法加上 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
55
56
57
58
/*
取款的任务类
*/
public class MyRun implements Runnable{
// 为了让run方法中,可以操作 Account对象,所以需要设计一个成员变量,并要求通过构造方法给该成员变量存值
private Account a;

public MyRun(Account a) {
this.a = a;
}
public void run() {
// 操作Account对象中的余额
// 获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+"即将开始判断账户余额");
a.qq(name);
}
}

/*
取款的账户类
*/
public class Account {
private double money;

public Account(double money) {
this.money = money;
}

public double getMoney() {
return money;
}

public void setMoney(double money) {
this.money = money;
}

public synchronized void qq(String name){
if(money > 0){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额
money-=1000;
// 有余额,可以取款
// 提示剩余的余额
System.out.println(name+"取出了"+1000+
"元钱!,剩余:"+money);
}else {
// 没有余额,取款失败
System.out.println(name+"取款失败!");
}
}
}

// 测试类不变

Lock接口

  • 概述
    • lock接口是对synchronized的优化,可以保证既能锁定的代码范围灵活,也能保证代码的可读性;但是不能自动释放锁,所以为了保证释放锁的行为一定要执行,需要使用 try{}finally{}代码块把释放锁的行为放在 finally中完成;

参考代码

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 MyRun implements Runnable {
// 提前准备一个锁对象
Lock l = new ReentrantLock();

// 为了让run方法中,可以操作 Account对象,所以需要设计一个成员变量,并要求通过构造方法给该成员变量存值
private Account a;

public MyRun(Account a) {
this.a = a;
}

public void run() {
// 操作Account对象中的余额
// 获取线程的名称
String name = Thread.currentThread().getName();
System.out.println(name + "即将开始判断账户余额");
// 开始锁代码
try {
l.lock();
System.out.println(name+"已经进入锁");
if (a.getMoney() > 0) {
Thread.sleep(5000);
// 修改余额
a.setMoney(a.getMoney() - 1000);
// 有余额,可以取款
// 提示剩余的余额
System.out.println(name + "取出了" + 1000 +
"元钱!,剩余:" + a.getMoney());
} else {
// 没有余额,取款失败
System.out.println(name + "取款失败!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
l.unlock();
}
}
}

线程通信

  • 概述

    • 就是多个线程在操作共享资源的时候,可以相互通信,根据不同线程的状态,协调完成任务 ;
  • 通信相关的方法

image-20230529121501845

注意事项

只有锁对象,才能调用上面的方法,否则出异常;

包子案例的思路分析

image-20230529143301211

包子案例的代码实现

共享资源类

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
/*
桌子类;(共享资源)
*/
public class Desk {
private String bz;
Random r = new Random();

// 生产包子的方法,将来会由厨师不停的调用;
public void create() {
String name = Thread.currentThread().getName();
// 根据 包子的状态,决定当前这个厨师有没有生产包子的资格
synchronized (this) {
this.notifyAll();// 唤醒别的线程
if (bz != null) {
// 说明包子还没有被吃掉,当前这个厨师线程,需要等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 说明没有包子,需要生产包子
bz = name + "生产了一个:" + r.nextInt(100) + "号包子!";
System.out.println(bz);
}
}
}
// 吃包子的方法,将来会由吃货不停的调用;
public void chi() {
String name = Thread.currentThread().getName();
// 根据 包子的状态,决定当前这个厨师有没有生产包子的资格
synchronized (this) {
this.notifyAll();// 唤醒别的线程
if (bz == null) {
// 说明包子还没有被做出来,当前这个吃货线程,需要等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 说明有包子,需要吃包子
System.out.println(name+"吃了:=======>"+bz);
bz =null;
}
}
}
}

厨师任务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
厨师任务类
*/
public class ChuShi implements Runnable{

private Desk d;

public ChuShi(Desk d) {
this.d = d;
}

@Override
public void run() {
while (true){
d.create();
}
}
}

吃货任务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
吃货线程
*/
public class ChiHuo implements Runnable{

private Desk d;

public ChiHuo(Desk d) {
this.d = d;
}

@Override
public void run() {
while (true){
d.chi();
}
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
测试类

1个共享资源
2个任务对象
5个线程对象

*/
public class Test {
public static void main(String[] args) {
// 1个共享资源
Desk d = new Desk();
// 2: 2个任务对象
ChuShi cs= new ChuShi(d);
ChiHuo ch = new ChiHuo(d);
// 5个线程对象
new Thread(cs,"厨师1").start();
new Thread(cs,"厨师2").start();
new Thread(cs,"厨师3").start();
new Thread(ch,"吃货1").start();
new Thread(ch,"吃货2").start();
}
}

线程池

  • 概述

    • 就是保存多个线程对象的池子;
  • 作用

  1. 可以提升程序的运行性能;(重复利用线程,节约了频繁创建和销毁线程对象的时间)
  2. 可以保证服务器的压力;(当达到规定的上限时,可以拒绝服务)
  • 线程池接口规范

    • ExecutorService
  • 实现类

    • ThreadPoolExecutor
  • 实现类的7个参数介绍

image-20230529153815359

  • 获取线程池的两种方式
    • 自己直接new ThreadPoolExecutor(7个参数);// 这是推荐的方式,因为更安全,配置的更详细;
    • 利用工具类 Executors中的静态方法 ;(不推荐,有风险)

自己创建线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
练习使用方式1 创建线程池对象
*/
public class MyPool {
public static void main(String[] args) {
// 1: 手动创建线程池对象
ExecutorService es = new ThreadPoolExecutor(2, 3, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), new ThreadFactory() {
Random ran = new Random();
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"我们自定义的线程"+ran.nextInt(100));
}
},new ThreadPoolExecutor.AbortPolicy());

System.out.println(es);

es.shutdown();
}
}

  • 线程池的常用方法

image-20230529162024934

线程池执行不带返回值的任务

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
package com.itheima.demo08_ThreadPool;

import java.util.Random;
import java.util.concurrent.*;

/*
练习使用方式1 创建线程池对象
后,让线程池执行 任务
*/
public class MyPool2 {
public static void main(String[] args) {
// 1: 手动创建线程池对象
ExecutorService es = new ThreadPoolExecutor(2, 3, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), new ThreadFactory() {
Random ran = new Random();
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"我们自定义的线程"+ran.nextInt(100));
}
},new ThreadPoolExecutor.AbortPolicy());

Runnable r = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"正在处理任务中....");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"处理任务完成....");
}
};
// 使用匿名内部类的方式,提交一个任务
for (int i = 0; i < 9; i++) {
es.execute(r);
}
es.shutdown();
}
}

线程池执行带返回值的任务

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
package com.itheima.demo08_ThreadPool;

import java.util.Random;
import java.util.concurrent.*;

/*
练习使用方式1 创建线程池对象
后,让线程池执行 带返回值的任务
*/
public class MyPool3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1: 手动创建线程池对象
ExecutorService es = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());

Callable<String> c = ()->{
String name = Thread.currentThread().getName();
System.out.println(name+"正在处理任务中...");
Thread.sleep(2000);
return name+"处理任务完成了";
};
// 使用匿名内部类的方式,提交一个任务
Future<String> f1 = es.submit(c);
Future<String> f2 = es.submit(c);
Future<String> f3 = es.submit(c);
Future<String> f4 = es.submit(c);
Future<String> f5 = es.submit(c);

String res1 = f1.get();
String res2 = f2.get();
String res3 = f3.get();
String res4 = f4.get();
String res5 = f5.get();
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
System.out.println(res4);
System.out.println(res5);

es.shutdown();
}
}

Executors工具类(了解)

  • 概述
1
可以帮我们快速创建线程池对象,但是不安全,不推荐使用;
  • 常用方法

image-20230529165512580

线程状态和转换

image-20230529165642951

当懒汉式单列设计模式遇上多线程

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
/*
使用懒汉式设计单列设计模式
*/
public class A {

// 构造方法私有
private A(){

}
// 2: 提前准备一个静态的成员变量,u用于保存我们自己创建的对象,外部可以通过这个成员变量,获取对象
private static A a;

// 3: 提供一个静态方法,让别人通过这个方法,获取a对象


public static A getA() {
// 外面的判断是为了一但对象创建后,后面所有获取对象的行为都无需在经历抢锁的代码了
if(a == null){
synchronized (A.class){
// 里面的判断是为了保证刚开始前几个线程可能都会进入19行的if,为了保证安全,所以再次进行了判断
if(a==null){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a=new A();
}
}
}
return a;
}
}