黄牛卖票和模拟龟兔赛跑问题分析
# 数据共享带来的并发问题
多线程有两个经典案例,12306买票问题和上厕所问题😂
一份资源,多个线程共享,会带来并发问题(数据的不一致性)
在实例化Web12306的时候,要想达到20张票,3个黄牛同时在卖,就只能实例化一次;
- 如果实例化三次,则成为每个黄牛在卖20张票,不会产生并发问题,因为此时每一个线程都得带到了一份资源
为了模拟买票时的网络延迟,Sleep 200ms
但得的结果却是:【不存在的票】
20份票的资源给3个黄牛来卖,票显然不会出现负数,这就是并发带来的数据不一致的问题
具体的原因:
首先我们要明白的一点就是,CPU执行的速度非常的快,用ms甚至是ns来衡量
当只剩最后一张票时,假设线程1、线程2、线程3分别被CPU调度:
- tickets = 1 > 0,线程1先进入到while循环中,休眠200ms,交出了CPU执行权;
- 此时,CPU空闲,调度线程2,由于线程1在休眠中还没有卖出票,此时 tickets = 1;
- 线程2进入while循环,也休眠200ms。此时tickets仍然等于1;
- 由于CPU切换的速度非常快,此时线程1和线程2仍然在休眠中,CPU调度线程3;
- 线程3进入while循环,同样休眠200ms
- 线程1最先休眠,所以最先醒来。执行
tickets--
操作,tickets = 0
,此时线程2和线程3仍在休眠中 - 线程2睡眠完成,由于线程2和线程3此时已经进入到了循环中,所以可以继续执行。线程2执行
tickets--
操作,tickets = -1
; - 同理,线程三醒来后又执行
tickets--
,tickets = -2
还有可能得到的结果是:【卖重复的票】
同一份资源,被三个线程同时拿到
每个线程在工作的时候,都会在相应的栈中执行,一个线程开辟一个栈空间(忘了的伙伴看看上面线程执行的内存图),它们从内存中读取数据。
比如在卖第10张票时,A线程从主存中拿到了10;由于CPU的操作非常的块,在A把10拿到他的工作区间的时候,线程B和线程C也同时从主存中拿到了各自的工作区间;所以就导致第10张票被卖了3次...
注意:
线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了对CPU的执行权,让其他的线程只能等待当前占有CPU的线程执行完其所有的操作,其他线程才能获取资源(等待当前线程卖完票,其他线程在进行卖票)
保证:同一时刻只有一个线程在卖票,即在上面的例子中,只有一个线程在while循环中进行买票
所以,针对此问题,Java中提出了锁机制
通过synchronized
关键字来给对象上锁
# 多线程模拟实现龟兔赛跑
故事背景:
从前有座山,山上有个乌龟和兔子,然后他们比赛,然后兔子睡着了,然后乌龟赢了,然后就没有然后了....
class Running implements Runnable {
private String winner; //获胜者名字
@Override
public void run() {
for(int steps = 1;steps<=100;steps++) {
//模拟兔子休息,让兔子每走10步睡300ms
if(Thread.currentThread().getName().equals("rabbit") && steps%10==0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() +"---->" + steps);
//判断比赛是否结束
boolean flag = isOver(steps);
if(flag)
break;
}
}
private boolean isOver(int steps) {
if(winner != null)
return true;
else {
if(steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("\n***** Winner is " + winner + " *****");
return true;
}
}
return false;
}
}
public class ThreadExample {
public static void main(String[] args) {
Running run = new Running();
new Thread(run,"tortoise").start();
new Thread(run,"rabbit").start();
}
}
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
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
最终,兔子走了10步,乌龟走了100步,Winner为 乌龟
这个故事告诉我们:
- 腿长的不一定走得快
- 时间再短也可以睡觉
- 在这个故事里,赢得总会是乌龟;因为编故事的人不想让兔子赢,不然还比什么赛呀....兔子要想变的像博尔特辣样快的蓝人,还和乌龟比什么,直接去找猎豹比了,破釜沉舟...
编辑 (opens new window)
上次更新: 2021/06/27, 10:49:09