面向对象一文搞定,终生难忘(面向对象心得)
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
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小时内删除侵权内容。