Java基础 第四节 第二课

网友投稿 447 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

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基础 第四节 第二课

要解决上述多线程并发访问一个资源的安全性问题. 也就是解决重复票与不存在票问题. 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——迅雷笔试题+知识点总结
相关文章