实例:多线程实现网络购票,用户提交购票信息后,
第一步:网站修改网站车票数据
第二部;显示出票反馈信息给用户
线程类:
public class Site implements Runnable {
//记录剩余票数
private int count = 10;
//记录当前抢到第几张票
private int num = 0;
public void run() {
//循环,当剩余票数为0时,结束
while (true) {
if (count <= 0) {
break;
} else {
// 1、修改数据(剩余票数,抢到第几张票)
count--;
// 2、显示信息,反馈用户抢到第几张票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
Site site = new Site();
Thread person1 = new Thread(site);
Thread person2 = new Thread(site);
Thread person3 = new Thread(site);
person1.start();
person2.start();
person3.start();
}
}
运行结果:
Thread-2抢到第3张票,剩余7张票
Thread-0抢到第3张票,剩余7张票
Thread-1抢到第3张票,剩余7张票
Thread-2抢到第6张票,剩余4张票
Thread-1抢到第6张票,剩余4张票
Thread-0抢到第6张票,剩余4张票
Thread-2抢到第9张票,剩余1张票
Thread-0抢到第9张票,剩余1张票
Thread-1抢到第9张票,剩余1张票
Thread-2抢到第10张票,剩余0张票
从运行结果看,简直不能看,输出的数据都是些什么东西啊。存在的问题如下:
1、不是从第一张票开始
2、存在多人抢到一张票的情况
3、有些票号没有被抢到
当多个线程共享同一资源时,一个线程未完成全部操作的时候,其他线程修改数据,造成数据的不安全问题。
原因分析:
第一个线程在休眠的1000ms中,其他的两个线程也完成了抢票动作然后进入休眠。等第一个线程一觉醒来一脸懵逼地发现只剩七张票了。剩下两个线程也是这样。
解决思路:将这两步操作绑定在一起,只有当一个线程全部完成这些操作时,才能允许其他线程进行操作。
修改
1、当用synchronized关键字来修改run方法时:
public synchronized void run() {
//循环,当剩余票数为0时,结束
while (true) {
if (count == 0) {
break;
} else {
// 1、修改数据(剩余票数,抢到第几张票)
count--;
// 2、显示信息,反馈用户抢到第几张票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
}
}
}
输出结果为:
Thread-0抢到第1张票,剩余9张票
Thread-0抢到第2张票,剩余8张票
Thread-0抢到第3张票,剩余7张票
Thread-0抢到第4张票,剩余6张票
Thread-0抢到第5张票,剩余5张票
Thread-0抢到第6张票,剩余4张票
Thread-0抢到第7张票,剩余3张票
Thread-0抢到第8张票,剩余2张票
Thread-0抢到第9张票,剩余1张票
Thread-0抢到第10张票,剩余0张票
此时第一个线程抢到了全部的票。因为第一个线程一直在run方法中的while中循环,直到票被抢完才会终止。此时运行第二和第三个线程的时候没有票可抢。
2、但是将代码修改为以下时:
public void run() {
//循环,当剩余票数为0时,结束
while (true) {
if (count <= 0) {
break;
} else {
this.a();
}
}
}
synchronized public void a() {
// 1、修改数据(剩余票数,抢到第几张票)
count--;
// 2、显示信息,反馈用户抢到第几张票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
}
输出结果为:
Thread-0抢到第1张票,剩余9张票
Thread-2抢到第2张票,剩余8张票
Thread-1抢到第3张票,剩余7张票
Thread-1抢到第4张票,剩余6张票
Thread-1抢到第5张票,剩余5张票
Thread-1抢到第6张票,剩余4张票
Thread-1抢到第7张票,剩余3张票
Thread-1抢到第8张票,剩余2张票
Thread-1抢到第9张票,剩余1张票
Thread-1抢到第10张票,剩余0张票
Thread-2抢到第11张票,剩余-1张票
Thread-0抢到第12张票,剩余-2张票
从结果来看,多抢了两张票。
当把if的控制条件改为以下时:
public void run() {
//循环,当剩余票数为0时,结束
while (true) {
if (count <= 2) {
break;
} else {
this.a();
}
}
}
输出结果为:
Thread-0抢到第1张票,剩余9张票
Thread-2抢到第2张票,剩余8张票
Thread-2抢到第3张票,剩余7张票
Thread-1抢到第4张票,剩余6张票
Thread-1抢到第5张票,剩余5张票
Thread-2抢到第6张票,剩余4张票
Thread-2抢到第7张票,剩余3张票
Thread-2抢到第8张票,剩余2张票
Thread-0抢到第9张票,剩余1张票
Thread-1抢到第10张票,剩余0张票
此时的结果没问题,但是为什么啊?在我看来,控制条件无论如何也不应该是 <= 2。
原因可能如下:三个线程都会运行run方法,在运行过程中都走过了if的判断条件,只是在else中的语句前停下,因为else中的语句设置了锁,在他们等待运行的时间中,票数可能就改变了,变得小于0了,但是他们还是会运行完自己的语句。直到下一次运行进不去else的条件时为止。
同时,如果将条件改为count == 0的话,结果可能会更糟,因为在他们等待的时间中,票数可能已经变化过去0了,一旦这样的话就会陷入到一个死循环中。
3、同时这样改也没问题:定义了一个flag来控制while循环条件
public class Site implements Runnable {
//记录剩余票数
private int count = 10;
//记录当前抢到第几张票
private int num = 0;
boolean flag = false;
public void run() {
//循环,当剩余票数为0时,结束
while (!false) {
this.a();
}
}
synchronized public void a() {
if (count <= 0) {
flag = true;
return;
} else {
}
// 1、修改数据(剩余票数,抢到第几张票)
count--;
// 2、显示信息,反馈用户抢到第几张票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
}
}
此时结果为:
Thread-0抢到第1张票,剩余9张票
Thread-2抢到第2张票,剩余8张票
Thread-2抢到第3张票,剩余7张票
Thread-2抢到第4张票,剩余6张票
Thread-1抢到第5张票,剩余5张票
Thread-1抢到第6张票,剩余4张票
Thread-1抢到第7张票,剩余3张票
Thread-2抢到第8张票,剩余2张票
Thread-2抢到第9张票,剩余1张票
Thread-2抢到第10张票,剩余0张票
4、使用修改代码块的方式来修改如下:
public class Site implements Runnable {
//记录剩余票数
private int count = 10;
//记录当前抢到第几张票
private int num = 0;
boolean flag = false;
public void run() {
//循环,当剩余票数为0时,结束
while (!flag) {
synchronized (this) {
this.a();
}
}
}
synchronized public void a() {
if (count <= 0) {
flag = true;
return;
} else {
// 1、修改数据(剩余票数,抢到第几张票)
count--;
// 2、显示信息,反馈用户抢到第几张票
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
}
}
}
运行结果如下:
Thread-0抢到第1张票,剩余9张票
Thread-0抢到第2张票,剩余8张票
Thread-0抢到第3张票,剩余7张票
Thread-0抢到第4张票,剩余6张票
Thread-0抢到第5张票,剩余5张票
Thread-0抢到第6张票,剩余4张票
Thread-2抢到第7张票,剩余3张票
Thread-1抢到第8张票,剩余2张票
Thread-1抢到第9张票,剩余1张票
Thread-1抢到第10张票,剩余0张票
总结
多个并发线程访问同一资源的同步代码块时
1、同一时刻只能有一个线程进入synchronized同步代码块。
2、当一个线程访问一个synchronized(this),其他synchronized(this)同步代码块同样被锁定
3、当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码。
什么锁,其实就是个厕所,你不出来我就进不去。