线程安全问题
# 简单抢票代码分析
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
/**
* @see Thread#run()
*/
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (ticketNum > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张车票");
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
// 定义一个线程对象
BuyTicketThread t = new BuyTicketThread();
// 窗口1买票
Thread t1 = new Thread(t, "窗口1");
t1.start();
// 窗口2买票
Thread t2 = new Thread(t, "窗口2");
t2.start();
// 窗口3买票
Thread t3 = new Thread(t, "窗口3");
t3.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
当前排队抢票代码看起来挺正常的。但是可能会发现如下问题:
- 可能会出现两个第 10 张票,或者出现三个第 10 张票
- 可能会出现第 0 张票、第-1 张票和第-2 张票
执行抢票的有 2 个流程
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张车票");
- 获取到
ticketNum
这个值 - 进行
ticketNum--
操作
会存在,线程抢占这 2 个操作的时候,不会同时进行。
分析
第一种情况:
线程 1:我在窗口 1 买到了第 10 张票,还没等到
--
操作,被线程 2 抢走了资源线程 2:我在窗口 2 买到了第 10 张票,因为线程 1 还没执行
--
操作,所以还是 10 张。线程 2 还没等到--
操作,也被线程 3 抢走了资源线程 3:我在窗口 3 买到了第 10 张票,这个时候执行了
--
操作,此时票剩下 9 张线程 1:现在又抢到了资源,执行
--
,从 9 张剩下了 8 张线程 2:现在也抢到了资源,执行
--
,从 8 张剩下了 7 张第二种情况:
现在就剩下 1 张票的情况
线程 1:我在窗口 1 买到了第 1 张票,还没等到
--
操作,被线程 2 抢走了资源线程 2:我在窗口 2 买到了第 x 张票,还没打印,就被线程 3 抢走了资源
线程 3:我在窗口 3 买到了第 x 张票,还没打印,就被线程 1 抢走了资源
线程 1:抢过来就继续执行
--
操作,现在票数为 0线程 2:也会抢回资源执行
--
操作,现在票数就会变成-1
,这是不被允许的情况线程 3:也抢回了资源执行
--
操作,现在票数就会变成-2
,这也是不被允许出现的情况
上面的代码出现问题,出现了重票、错票:即线程安全引起的问题
原因:多个线程,在争抢资源的过程中,导致共享的资源出现问题,一个线程还没执行完,另一个线程就参与进来了,开始争抢。
# 解决
案例:现在就一个厕所,很多人都想上厕所,第一个人进去的就会把门关上,加一把锁,别的人都进不去,就进行等待。可以保证一次厕所只能有一个人。
所以我们也可以在程序中在进行抢占资源的地方进行加锁,或者说加同步监视器
# 方法 1:同步代码块
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
/**
* @see Thread#run()
*/
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
// 加锁
synchronized (this) {
if (ticketNum > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张车票");
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
疑问点
为什么锁加在了for
里面的判断上,而不加在for
上面?
如果加在了上面,这就表示这 100 个人,这就相当于,你给这 100 个人都加上了锁,100 个人到了一个窗口 1 进行买票,这 100 个人都得等到买完了,其他的窗口才能继续;
所以把锁加在了抢票的代码上即可,无论有多少人在抢,你只要在对应的窗口等着就行。
我们只需要在具有安全隐患的代码锁住即可,如果锁多了,就会效率低
synchronized (this)
-> this
就是这个锁