什么是线程安全?
- 当多个线程同时共享,同一个全局变量或静态变量。做写的操作时(增、删、改),可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
- 比如说,我们现在去上厕所。就两个厕所,但是好几十人急着上厕所。然后那个厕所又没有里面锁头,你上着厕所蹲着茅坑了,别人还推开们抢你的坑位。(这样子拉屎就不安全,需要一个锁头,别人就进不来了,需要等待)
例子
- 现在有 30 张火车票,有三个窗口同时抢火车票,请使用多线程模拟抢票效果。
/**
* @Author : PengPeng
* 线程 不安全 的抢票
* 现在有 30 张火车票,有三个窗口同时抢火车票,请使用多线程模拟抢票效果。
*/
public class Test001 {
public static void main(String[] args) {
ThreadDemo101 threadDemo101 = new ThreadDemo101();
Thread t1 = new Thread(threadDemo101, "窗口一");
Thread t2 = new Thread(threadDemo101, "窗口二");
Thread t3 = new Thread(threadDemo101, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
class ThreadDemo101 implements Runnable {
private int fare = 30;
@Override
public void run() {
// 有票就一直循环卖!
while (fare > 0) {
sale();
}
}
private void sale() {
// 有票继续卖
if (fare > 0) {
fare--;
System.out.println(Thread.currentThread().getName() + ":卖出弟 " + (30 - fare) + " 张,还剩: " + fare + "张。");
}
}
}
- 执行结果
窗口二:卖出弟 2 张,还剩: 28张。
窗口二:卖出弟 4 张,还剩: 26张。
窗口二:卖出弟 5 张,还剩: 25张。
窗口一:卖出弟 2 张,还剩: 28张。
窗口三:卖出弟 3 张,还剩: 27张。
窗口三:卖出弟 8 张,还剩: 22张。
窗口三:卖出弟 9 张,还剩: 21张。
窗口三:卖出弟 10 张,还剩: 20张。
窗口一:卖出弟 7 张,还剩: 23张。
窗口二:卖出弟 6 张,还剩: 24张。
窗口一:卖出弟 12 张,还剩: 18张。
窗口三:卖出弟 11 张,还剩: 19张。
窗口一:卖出弟 14 张,还剩: 16张。
窗口二:卖出弟 13 张,还剩: 17张。
窗口一:卖出弟 16 张,还剩: 14张。
窗口三:卖出弟 15 张,还剩: 15张。
窗口一:卖出弟 18 张,还剩: 12张。
窗口二:卖出弟 17 张,还剩: 13张。
窗口一:卖出弟 20 张,还剩: 10张。
窗口三:卖出弟 19 张,还剩: 11张。
窗口一:卖出弟 22 张,还剩: 8张。
窗口二:卖出弟 21 张,还剩: 9张。
窗口一:卖出弟 24 张,还剩: 6张。
窗口三:卖出弟 23 张,还剩: 7张。
窗口一:卖出弟 26 张,还剩: 4张。
窗口二:卖出弟 25 张,还剩: 5张。
窗口一:卖出弟 28 张,还剩: 2张。
窗口三:卖出弟 27 张,还剩: 3张。
窗口一:卖出弟 30 张,还剩: 0张。
窗口二:卖出弟 29 张,还剩: 1张。
Process finished with exit code 0
- 很明显上面的代码出现了
线程安全问题
,有的票被多次卖出。
线程安全解决办法:
-
如何解决多线程之间线程安全问题
使用 synchronized 关键字或使用 lock 锁。 -
为什么使用 synchronized 关键字或使用 lock 锁 就能解决线程安全问题呢?
将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。 -
什么是多线程之间同步
当多个线程共享同一个资源,不会受到其他线程的干扰。
解决上述线程不安全的代码
- 加入 synchronized 关键字
/**
* @Author : PengPeng
* 线程 安全 的抢票
* 现在有 30 张火车票,有三个窗口同时抢火车票,请使用多线程模拟抢票效果。
*/
public class Test002 {
public static void main(String[] args) {
ThreadDemo102 ThreadDemo102 = new ThreadDemo102();
Thread t1 = new Thread(ThreadDemo102, "窗口四");
Thread t2 = new Thread(ThreadDemo102, "窗口五");
Thread t3 = new Thread(ThreadDemo102, "窗口六");
t1.start();
t2.start();
t3.start();
}
}
class ThreadDemo102 implements Runnable {
private int fare = 30;
@Override
public void run() {
// 有票就一直循环卖!
while (fare > 0) {
sale();
}
}
private synchronized void sale() {
// 有票继续卖
if (fare > 0) {
fare--;
System.out.println(Thread.currentThread().getName() + ":卖出了" + (30 - fare) + "张,还剩:" + fare + "张。");
}
}
}
窗口四:卖出了1张,还剩:29张。
窗口四:卖出了2张,还剩:28张。
窗口四:卖出了3张,还剩:27张。
窗口四:卖出了4张,还剩:26张。
窗口六:卖出了5张,还剩:25张。
窗口六:卖出了6张,还剩:24张。
窗口六:卖出了7张,还剩:23张。
窗口六:卖出了8张,还剩:22张。
窗口六:卖出了9张,还剩:21张。
窗口六:卖出了10张,还剩:20张。
窗口六:卖出了11张,还剩:19张。
窗口六:卖出了12张,还剩:18张。
窗口六:卖出了13张,还剩:17张。
窗口六:卖出了14张,还剩:16张。
窗口六:卖出了15张,还剩:15张。
窗口六:卖出了16张,还剩:14张。
窗口六:卖出了17张,还剩:13张。
窗口六:卖出了18张,还剩:12张。
窗口六:卖出了19张,还剩:11张。
窗口六:卖出了20张,还剩:10张。
窗口六:卖出了21张,还剩:9张。
窗口六:卖出了22张,还剩:8张。
窗口六:卖出了23张,还剩:7张。
窗口六:卖出了24张,还剩:6张。
窗口六:卖出了25张,还剩:5张。
窗口六:卖出了26张,还剩:4张。
窗口六:卖出了27张,还剩:3张。
窗口六:卖出了28张,还剩:2张。
窗口六:卖出了29张,还剩:1张。
窗口六:卖出了30张,还剩:0张。
Process finished with exit code 0
- 自己测试时建议票数改大点,可以更好的看到效果。