需求描述:四个窗口一起卖票,把10张票卖完,不许多卖

 

先看一个错误的案例:

package android.java.thread06;

/**
 * 售票线程
 */
class Booking extends Thread {

    /**
     * 模拟票的总算 10张票
     */
    private int ticket = 10;

    @Override
    public void run() {
        super.run();

        while (ticket > 0) {
            System.out.println("名称:" + Thread.currentThread().getName() + "窗口卖出第" + ticket + "张票");
            ticket -- ;
        }
    }
}

/**
 * 售票案例
 */
public class BookingTest {

    public static void main(String[] args) {

        // 实例化线程对象
        Thread thread1 = new Booking();
        Thread thread2 = new Booking();
        Thread thread3 = new Booking();
        Thread thread4 = new Booking();

        // 开启启动线程
        thread1.start(); // 启动第Thread-0窗口 执行卖票任务
        thread2.start(); // 启动第Thread-1窗口 执行卖票任务
        thread3.start(); // 启动第Thread-2窗口 执行卖票任务
        thread4.start(); // 启动第Thread-3窗口 执行卖票任务
    }

}

日志结果:

名称:Thread-0窗口卖出第10张票
名称:Thread-0窗口卖出第9张票
名称:Thread-0窗口卖出第8张票
名称:Thread-1窗口卖出第10张票
名称:Thread-0窗口卖出第7张票
名称:Thread-0窗口卖出第6张票
名称:Thread-0窗口卖出第5张票
名称:Thread-0窗口卖出第4张票
名称:Thread-0窗口卖出第3张票
名称:Thread-0窗口卖出第2张票
名称:Thread-0窗口卖出第1张票
名称:Thread-1窗口卖出第9张票
名称:Thread-1窗口卖出第8张票
名称:Thread-1窗口卖出第7张票
名称:Thread-1窗口卖出第6张票
名称:Thread-1窗口卖出第5张票
名称:Thread-1窗口卖出第4张票
名称:Thread-1窗口卖出第3张票
名称:Thread-1窗口卖出第2张票
名称:Thread-2窗口卖出第10张票
名称:Thread-2窗口卖出第9张票
名称:Thread-1窗口卖出第1张票
名称:Thread-2窗口卖出第8张票
名称:Thread-2窗口卖出第7张票
名称:Thread-3窗口卖出第10张票
名称:Thread-3窗口卖出第9张票
名称:Thread-2窗口卖出第6张票
名称:Thread-3窗口卖出第8张票
名称:Thread-3窗口卖出第7张票
名称:Thread-2窗口卖出第5张票
名称:Thread-3窗口卖出第6张票
名称:Thread-2窗口卖出第4张票
名称:Thread-3窗口卖出第5张票
名称:Thread-2窗口卖出第3张票
名称:Thread-3窗口卖出第4张票
名称:Thread-2窗口卖出第2张票
名称:Thread-2窗口卖出第1张票
名称:Thread-3窗口卖出第3张票
名称:Thread-3窗口卖出第2张票
名称:Thread-3窗口卖出第1张票

从日志结果来看,没有实现需求,反而多卖了30张,例如:本来一节车厢坐10人,结果一节车厢卖了40张票,这是非常严重的错误

为什么会这样呢,看内存图就明白了:

由于 Thread-0线程有自己的ticket变量

   Thread-1线程有自己的ticket变量 

   Thread-2线程有自己的ticket变量 

   Thread-3线程有自己的ticket变量  

     所以才会造成40张票 

 


 

 

 

 使用static:把ticket定义为static变量,ticket会跑到静态区,ticket就被四个线程对象共用

 

package android.java.thread06;

/**
 * 售票线程
 */
class Booking extends Thread {

    /**
     * 模拟票的总算 10张票 (定义静态变量,此变量会跑到静态区域,成为公用变量)
     */
    private static int ticket = 10;

    @Override
    public void run() {
        super.run();

        while (ticket > 0) {
            System.out.println("名称:" + Thread.currentThread().getName() + "窗口卖出第" + ticket + "张票");
            ticket -- ;
        }
    }
}

/**
 * 售票案例
 */
public class BookingTest {

    public static void main(String[] args) {

        // 实例化线程对象
        Thread thread1 = new Booking();
        Thread thread2 = new Booking();
        Thread thread3 = new Booking();
        Thread thread4 = new Booking();

        // 开启启动线程
        thread1.start(); // 启动第Thread-0窗口 执行卖票任务
        thread2.start(); // 启动第Thread-1窗口 执行卖票任务
        thread3.start(); // 启动第Thread-2窗口 执行卖票任务
        thread4.start(); // 启动第Thread-3窗口 执行卖票任务
    }

}

 

结果出现相同的票,这是CPU非常非常快速切换造成的:

结果只出现了 Thread-0  和 Thread-1 那是因为 Thread-0 已经把ticket-- 到 0了,所以无法其他的线程对象无法进入while循环,也就无法看到打印

 

 


 

 

解决CPU快速切换四个线程,导致重复卖票的问题,加同步

package android.java.thread06;

/**
 * 售票线程
 */
class Booking extends Thread {

    /**
     * 模拟票的总算 10张票 (定义静态变量,此变量会跑到静态区域,成为公用变量)
     */
    private static int ticket = 10;

    @Override
    public void run() {
        super.run();

        while (ticket > 0) {

            /**
             * 加入同步标识,进行拦截,例如:我在ticket-- =9 的时候 其他线程不准ticket-- =9,这样就解决了重复--值的问题
             */
            synchronized (Booking.class) {
                /**
                 * 必须在同步里面再次判断 ticket > 0 , 因为CUP对四个线程切换太快 有可能ticket=-1 或 ticket=-2 ... ,所以必须再次判断
                 */
                if (ticket > 0) {
                    System.out.println("名称:" + Thread.currentThread().getName() + "窗口卖出第" + ticket + "张票");
                    ticket--;
                }
            }
        }

    }
}

/**
 * 售票案例
 */
public class BookingTest {

    public static void main(String[] args) {

        // 实例化线程对象
        Thread thread1 = new Booking();
        Thread thread2 = new Booking();
        Thread thread3 = new Booking();
        Thread thread4 = new Booking();

        // 开启启动线程
        thread1.start(); // 启动第Thread-0窗口 执行卖票任务
        thread2.start(); // 启动第Thread-1窗口 执行卖票任务
        thread3.start(); // 启动第Thread-2窗口 执行卖票任务
        thread4.start(); // 启动第Thread-3窗口 执行卖票任务
    }

}

 

执行结果:已经满足需求