Java基础 第四节 第二课

网友投稿 415 2022-05-30

线程安全

概述

案例

模拟票

测试类

线程同步

同步代码块

格式

同步锁

同步方法

格式

代码

Lock 锁

概述

如果有过个线程在同时运行, 而这些线程可能会勇士运行这段代码. 程序每次运行结果和单线程运行的结果是一样的, 而其他的变量的值也和预期的是一样的, 就是线程安全的.

案例

我们通过一个案例, 演示线程的安全问题:

电影院要卖票, 我们模拟电影院的卖过程. 假设要播放的电影是 “郭德纲和他嫂子的爱情故事”. 本次电影的座位共有 100 个. (本场电影只能卖 100 张票)

我们来模拟电影院的售票窗口, 实现多个窗口同时卖 “郭德纲和他嫂子的爱情故事” 这场电影票. (多个窗口一起卖这 100 张票)

窗口采用线程对象来模拟, 票采用 Runnable 接口子类来模拟.

模拟票

public class Ticket implements Runnable { private int ticket = 100; /** * 执行卖票操作 */ @Override public void run() { // 每个窗口卖票的操作 // 窗口永远开启 while (true) { if (ticket > 0) { // 有票可卖 // 出票操作 // 使用sleep模拟一下出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖: " + ticket--); } } } }

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

测试类

public class Test51 { public static void main(String[] args) { // 创建线程任务对象 Ticket ticket = new Ticket(); // 创建三个窗口对象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); // 同时卖票 t1.start(); t2.start(); t3.start(); } } 输出结果: 窗口1正在卖: 99 窗口3正在卖: 98 窗口2正在卖: 100 窗口2正在卖: 97 窗口1正在卖: 96 窗口3正在卖: 95 窗口2正在卖: 94 窗口1正在卖: 93 窗口3正在卖: 92 窗口3正在卖: 91 窗口1正在卖: 90 窗口2正在卖: 89 窗口2正在卖: 88 窗口3正在卖: 86 窗口1正在卖: 87 窗口1正在卖: 85 窗口2正在卖: 84 窗口3正在卖: 83 窗口1正在卖: 82 窗口2正在卖: 81 窗口3正在卖: 80 窗口3正在卖: 79 窗口2正在卖: 78 窗口1正在卖: 77 窗口3正在卖: 76 窗口2正在卖: 75 窗口1正在卖: 74 窗口1正在卖: 73 窗口2正在卖: 72 窗口3正在卖: 71 窗口1正在卖: 70 窗口2正在卖: 69 窗口3正在卖: 68 窗口3正在卖: 67 窗口1正在卖: 65 窗口2正在卖: 66 窗口1正在卖: 64 窗口2正在卖: 62 窗口3正在卖: 63 窗口3正在卖: 61 窗口1正在卖: 60 窗口2正在卖: 59 窗口2正在卖: 58 窗口1正在卖: 57 窗口3正在卖: 56 窗口3正在卖: 55 窗口2正在卖: 53 窗口1正在卖: 54 窗口1正在卖: 52 窗口3正在卖: 51 窗口2正在卖: 50 窗口1正在卖: 49 窗口2正在卖: 48 窗口3正在卖: 47 窗口2正在卖: 46 窗口3正在卖: 45 窗口1正在卖: 44 窗口1正在卖: 43 窗口3正在卖: 42 窗口2正在卖: 41 窗口2正在卖: 40 窗口3正在卖: 39 窗口1正在卖: 38 窗口1正在卖: 37 窗口3正在卖: 36 窗口2正在卖: 35 窗口1正在卖: 34 窗口3正在卖: 33 窗口2正在卖: 32 窗口2正在卖: 31 窗口3正在卖: 30 窗口1正在卖: 29 窗口3正在卖: 28 窗口1正在卖: 27 窗口2正在卖: 26 窗口2正在卖: 25 窗口3正在卖: 24 窗口1正在卖: 23 窗口3正在卖: 22 窗口2正在卖: 21 窗口1正在卖: 22 窗口2正在卖: 20 窗口1正在卖: 19 窗口3正在卖: 18 窗口3正在卖: 17 窗口2正在卖: 16 窗口1正在卖: 15 窗口3正在卖: 14 窗口2正在卖: 13 窗口1正在卖: 12 窗口1正在卖: 11 窗口2正在卖: 10 窗口3正在卖: 9 窗口3正在卖: 8 窗口2正在卖: 7 窗口1正在卖: 6 窗口2正在卖: 5 窗口1正在卖: 5 窗口3正在卖: 4 窗口2正在卖: 3 窗口1正在卖: 2 窗口3正在卖: 1 窗口2正在卖: 0 窗口1正在卖: -1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

Java基础 第四节 第二课

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

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

发现程序出现了两个问题:

相同的票数, 比如 5 这张票被卖了两回

不存在的票, 比如 0 票与 -1, 是不存在的

这种问题, 几个窗口 (线程)票数不同了, 这种问题成为线程不安全.

线程安全问题都是由全局变量及静态变量引起的. 若每个线程中对全局变量, 静态变量只有读操作, 而无写操作, 一般来说, 这个全局变量是线程安全的. 若有多个线程同时执行操作, 一般都需要考虑线程同步, 否则的话就可能影响线程安全.

线程同步

当我们使用多个线程访问同一资源的时候, 且多个线程中对资源有写的操作, 就容易出现线程安全问题.

要解决上述多线程并发访问一个资源的安全性问题. 也就是解决重复票与不存在票问题. Java 中提供了 (synchronized) 来解决.

根据案例描述:

窗口 1 线程进入操作的时候, 窗口 2 和窗口 3

线程只能在外等着. 窗口 1 操作结束, 窗口 1 和窗口 3有机会去执行. 也就是说在某个线程修改共享资源的时候, 其他线程不能去修改该资源, 等待修改完毕同步之后,才能去抢夺 CPU 资源, 完成对应的操作, 保证了数据的同步性, 解决了线程不安全的现象.

为了保证每个线程都能正常秩序原子操作 Java 引入了线程同步机制.

那么怎么去使用呢? 有三种方式完成同步操作:

同步代码块

同步方法

锁机制

同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中. 表示只对这个区块的资源实行互斥访问.

格式

synchronized(同步锁){ 需要同步操作的代码 }

1

2

3

同步锁

对象的同步锁只是一个概念, 可以想象为在对象上标记了一个锁:

锁对象, 可也是任意类型

多个线程对象, 要使用同一把锁

注: 在任何时候, 最多允许一个线程拥有同步锁. 谁拿到所就进入代码块. 其他的线程只能在外面等着. (Blocked)

使用同步代码块解决代码:

public class Ticket implements Runnable { private int ticket = 100; Object lock = new Object(); /** * 执行卖票操作 */ @Override public void run() { // 每个窗口卖票的操作 // 窗口, 永远开启 while (true) { synchronized (lock) { if (ticket > 0) { // 有票可卖 // 出票操作 // 使用sleep模拟一下出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖: " + ticket--); } } } } }

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

当使用了同步代码块后, 上述的线程的安全问题, 解决了.

同步方法

同步方法: 使用 synchronized 修饰的方法, 就叫做同步方法. 保证 A 线程执行该方法的时候, 其他线程只能在方法外等着.

格式

public synchronized void method(){ 可能会产生线程安全问题的代码 }

1

2

3

同步锁是谁?

对于非 static 方法, 同步锁就是 this. 对于 static 方法, 我们使用当前方法所在类的字节码对象 (类名.class)

代码

public class Ticket implements Runnable { private int ticket = 100; /** * 执行卖票操作 */ @Override public void run() { // 每个窗口卖票的操作 // 窗口永远开启 while (true){ } } /** * 锁对象是谁调用这个方法就是谁 * 隐含锁对象就是this */ public synchronized void sellTicket(){ if(ticket > 0){ // 有票可以卖 // 出票操作 // 使用sleep模拟一下出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖: " + ticket--); } } }

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

Lock 锁

java.util.concurrent.locks.Lock机制提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作, 同步代码块 / 同步方法具有功能 Lock 都有, 除此之外更强大, 更体现面向对象.

Lock 锁也称为同步锁, 加锁与释放锁方法如下:

public void lock(): 加同步锁

public void unlock(): 释放同步锁

使用如下:

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Ticket implements Runnable { private int ticket = 100; Lock lock = new ReentrantLock(); /** * 执行卖票操作 */ @Override public void run() { // 每个窗口卖票的操作 // 窗口永远开启 while (true){ lock.lock(); if(ticket > 0){ // 有票可以卖 // 出票操作 // 使用sleep模拟一下出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name + "正在卖: " + ticket--); } } } }

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

Java 任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring Boot+CAS 默认登录页面太丑了,怎么办?
下一篇:剑指Offer——迅雷笔试题+知识点总结
相关文章