线程安全
上一节的售票案例,极有可能碰到“意外”情况,如一张票被打印多次,或者打印出的票号为0甚至负数。这些“意外”都是由多线程操作共享资源tickets所导致的线程安全问题,接下来对案例进行修改,模拟四个窗口出售10张票,并在售票的代码中每次售票时线程休眠100毫秒,如文件1所示。
文件1 Example11.java
1 // 定义SaleThread类实现Runnable接口
2 class SaleThread implements Runnable {
3 private int tickets = 10; // 10张票
4 public void run() {
5 while (true) {
6 if (tickets > 0) {
7 try {
8 Thread.sleep(100); // 模拟售票耗时过程
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 }
12 System.out.println(Thread.currentThread().getName()
13 + " 正在发售第 " + tickets-- + " 张票 ");
14 }
15 }
16 }
17 }
18 public class Example11 {
19 public static void main(String[] args) {
20 SaleThread saleThread = new SaleThread();
21 // 创建并开启四个线程,模拟4个售票窗口
22 new Thread(saleThread, "窗口1").start();
23 new Thread(saleThread, "窗口2").start();
24 new Thread(saleThread, "窗口3").start();
25 new Thread(saleThread, "窗口4").start();
26 }
27 }
运行结果如图1所示。
图1 运行结果
图1中,最后几行打印售出的票为0和负数,这种现象是不应该出现的,因为在售票程序中做了判断只有当票号大于0时才会进行售票。运行结果中之所以出现了负数的票号是因为多线程在售票时出现了安全问题。
在售票程序的while循环中添加了sleep()方法,这样就模拟了售票过程中线程的延迟。由于线程有延迟,当票号减为1时,假设窗口2线程此时出售1号票,对票号进行判断后,进入while循环,在售票之前通过sleep()方法模拟售票时耗时操作,这时窗口1线程会进行售票,由于此时票号仍为1,因此窗口1线程也会进入循环,同理,四个线程都会进入while循环,休眠结束后,四个线程都会进行售票,这样就相当于将票号减了四次,结果中出现了1、0、-1、-2这样的票号。