Java基础 第四节 第二课
概述
如果有过个线程在同时运行, 而这些线程可能会勇士运行这段代码. 程序每次运行结果和单线程运行的结果是一样的, 而其他的变量的值也和预期的是一样的, 就是线程安全的.
案例
我们通过一个案例, 演示线程的安全问题:
电影院要卖票, 我们模拟电影院的卖过程. 假设要播放的电影是 “郭德纲和他嫂子的爱情故事”. 本次电影的座位共有 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
文章来源: iamarookie.blog.csdn.net,作者:我是小白呀,版权归原作者所有,如需转载,请联系作者。
原文链接:iamarookie.blog.csdn.net/article/details/111413489
- 点赞
- 收藏
- 关注作者
评论(0)