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