1.卖票案例需求分析
某天某个景区售票,门票票只有100张,景区有三个售票窗口,同时售票,共享票源(100张)
通过多线程的方式实现三个售票窗口同时售票
- 首先要明确票源只能有一个
- 其次需要创建三个Thread的对象去执行卖票的方法,卖完票后要将剩余的票数返回给统一的票源;
2.代码实现
1.票源和多线程执行入口代码
public class Ticket implements Runnable {
private int tickets = 100; //票源
TicketOffice ticketOffice = new TicketOffice();
@Override
public void run() {
while(true){
this.tickets = ticketOffice.sellTicket(tickets); //sellTicket由synchronized修饰
//返回的tickets重新传给成员变量this.tickets以保证票源的一致
}
}
}
2.售票规则代码
public class TicketOffice {
public synchronized int sellTicket(int tickets) {
if (tickets > 0) {
try {
Thread.sleep(0); //模拟CPU忙碌的停顿
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread th = Thread.currentThread();
System.out.println(th.getName() + " 线程数为:" + Thread.activeCount() + " 出售第:" + tickets--);
}
return tickets;
}
}
在案例中我把售票窗口代码写道了另一个类中,并设置为了静态方法,可以直接通过类名来调用
3.测试类代码
public class ThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket(); //票源固定100张
Thread thread0 = new Thread(ticket); //创建新线程
thread0.start();
Thread thread1 = new Thread(ticket);
thread1.start();
Thread thread2 = new Thread(ticket);
thread2.start();
}
2.1 结果分析
为了分析结果的方便,我们将票源修改为5张,观察结果是否出错
!!! 显然输出结果有问题,我们设置的票源总共有5张,而此时三个窗口总和加起来卖了15张!
2.2 问题分析
通过代码分析发现是synchronized锁的不够全面出现的,在售票规则代码块中售票方法被synchronized修饰,方法内部的共享数据tickets被锁住,一个线程为执行完方法时,另一线程不允许进入。==方法内部的“门”锁好了,但是忽略的run()中调用方法需要传形参的这道“门”!==由于ticketOffice.sellTicket(tickets);方法运行时间较长,在传入形参的时候有可能三个线程都进行了ticketOffice.sellTicket(tickets);中形参的赋值任务,从而导致会输出三个线程都从第5张票开始贩卖。因此我们需要在外部也就是this.tickets = ticketOffice.sellTicket(tickets);的外部也加上synchronized代码块,从而使得共享成员不会错误赋值。
3.代码修改
只需要在外部也就是this.tickets = ticketOffice.sellTicket(tickets);的外部也加上synchronized代码块即可;
public class Ticket implements Runnable {
private int tickets = 100;
TicketOffice ticketOffice = new TicketOffice();
@Override
public void run() {
while(true){
synchronized (this) {
this.tickets = ticketOffice.sellTicket(tickets); //sellTicket由synchronized修饰
}
}
}
}
这样运行出的结果就是正确的了
小结:大家在其他地方应该也看到过类似的案例,他们一般是把售票规则代码与run()方法放在同一个类中,这样会避免许多错误,这样就显得我的改动可能是画蛇添足,不过还是能从这样的修改中明白synchronized的使用位置,和文章中这种错误出现的原因所在;所以个人认为代码不能按着老师的写法来,学习代码我们需要有自己的思路,自己的方法,遇到问题需要用自己解决问题的方式。
问题:在运行过程中我发现,在没有synchronized修饰线程操作所共享数据时,输出结果中每个线程都有售出票的显示,而加入synchronized操作时结果显示虽然有3个(输出中显示5个,是因为包含了main和JVM虚拟机运行所使用的线程)线程在运行,但只有Thread-0在售票,很疑惑…