【Java并发编程三】多线程实现多个窗口卖票问题

写在前面: 我是「扬帆向海」,这个昵称来源于我的名字以及女朋友的名字。我热爱技术、热爱开源、热爱编程。技术是开源的、知识是共享的。

这博客是对自己学习的一点点总结及记录,如果您对 Java算法 感兴趣,可以关注我的动态,我们一起学习。

用知识改变命运,让我们的家人过上更好的生活

一、需求分析

利用多线程的思想模拟三个窗口售票员卖30张票的功能

火车站要售票,我们模拟火车站的售票过程。
假设正值春运时期,西安到兰州的动车票只有30张(西安火车站窗口只能卖30张票)。
我们采用线程对象来模拟火车站的售票窗口,实现多个窗口同时卖火车票, 采用Runnable接口子类来模拟票数。

二、代码实现

1.继承Thread类的方式

class TicketWindow extends Thread {
    // 车票数量
    private int ticket = 30;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
            } else {
                System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
                break;
            }
        }
    }
}

public class SellTicketTest1 {
    public static void main(String[] args) {
        TicketWindow tw1 = new TicketWindow();
        TicketWindow tw2 = new TicketWindow();
        TicketWindow tw3 = new TicketWindow();

        tw1.setName("窗口1");
        tw2.setName("窗口2");
        tw3.setName("窗口3");

        tw1.start();
        tw2.start();
        tw3.start();
    }
}

代码执行结果:

窗口1卖出了第30张票,剩余29张票
窗口1卖出了第29张票,剩余28张票
窗口1卖出了第28张票,剩余27张票
窗口1卖出了第27张票,剩余26张票
窗口1卖出了第26张票,剩余25张票
窗口1卖出了第25张票,剩余24张票
窗口1卖出了第24张票,剩余23张票
窗口1卖出了第23张票,剩余22张票
窗口1卖出了第22张票,剩余21张票
窗口1卖出了第21张票,剩余20张票
窗口1卖出了第20张票,剩余19张票
窗口1卖出了第19张票,剩余18张票
窗口1卖出了第18张票,剩余17张票
窗口1卖出了第17张票,剩余16张票
窗口1卖出了第16张票,剩余15张票
窗口1卖出了第15张票,剩余14张票
窗口1卖出了第14张票,剩余13张票
窗口1卖出了第13张票,剩余12张票
窗口1卖出了第12张票,剩余11张票
窗口1卖出了第11张票,剩余10张票
窗口1卖出了第10张票,剩余9张票
窗口1卖出了第9张票,剩余8张票
窗口1卖出了第8张票,剩余7张票
窗口1卖出了第7张票,剩余6张票
窗口1卖出了第6张票,剩余5张票
窗口1卖出了第5张票,剩余4张票
窗口1卖出了第4张票,剩余3张票
窗口1卖出了第3张票,剩余2张票
窗口1卖出了第2张票,剩余1张票
窗口1卖出了第1张票,剩余0张票
窗口1余票不足,停止售票!
窗口3卖出了第30张票,剩余29张票
窗口3卖出了第29张票,剩余28张票
窗口3卖出了第28张票,剩余27张票
窗口3卖出了第27张票,剩余26张票
窗口3卖出了第26张票,剩余25张票
窗口3卖出了第25张票,剩余24张票
窗口3卖出了第24张票,剩余23张票
窗口3卖出了第23张票,剩余22张票
窗口3卖出了第22张票,剩余21张票
窗口3卖出了第21张票,剩余20张票
窗口3卖出了第20张票,剩余19张票
窗口3卖出了第19张票,剩余18张票
窗口3卖出了第18张票,剩余17张票
窗口3卖出了第17张票,剩余16张票
窗口3卖出了第16张票,剩余15张票
窗口3卖出了第15张票,剩余14张票
窗口3卖出了第14张票,剩余13张票
窗口3卖出了第13张票,剩余12张票
窗口3卖出了第12张票,剩余11张票
窗口3卖出了第11张票,剩余10张票
窗口3卖出了第10张票,剩余9张票
窗口3卖出了第9张票,剩余8张票
窗口3卖出了第8张票,剩余7张票
窗口3卖出了第7张票,剩余6张票
窗口3卖出了第6张票,剩余5张票
窗口3卖出了第5张票,剩余4张票
窗口3卖出了第4张票,剩余3张票
窗口3卖出了第3张票,剩余2张票
窗口3卖出了第2张票,剩余1张票
窗口3卖出了第1张票,剩余0张票
窗口3余票不足,停止售票!
窗口2卖出了第30张票,剩余29张票
窗口2卖出了第29张票,剩余28张票
窗口2卖出了第28张票,剩余27张票
窗口2卖出了第27张票,剩余26张票
窗口2卖出了第26张票,剩余25张票
窗口2卖出了第25张票,剩余24张票
窗口2卖出了第24张票,剩余23张票
窗口2卖出了第23张票,剩余22张票
窗口2卖出了第22张票,剩余21张票
窗口2卖出了第21张票,剩余20张票
窗口2卖出了第20张票,剩余19张票
窗口2卖出了第19张票,剩余18张票
窗口2卖出了第18张票,剩余17张票
窗口2卖出了第17张票,剩余16张票
窗口2卖出了第16张票,剩余15张票
窗口2卖出了第15张票,剩余14张票
窗口2卖出了第14张票,剩余13张票
窗口2卖出了第13张票,剩余12张票
窗口2卖出了第12张票,剩余11张票
窗口2卖出了第11张票,剩余10张票
窗口2卖出了第10张票,剩余9张票
窗口2卖出了第9张票,剩余8张票
窗口2卖出了第8张票,剩余7张票
窗口2卖出了第7张票,剩余6张票
窗口2卖出了第6张票,剩余5张票
窗口2卖出了第5张票,剩余4张票
窗口2卖出了第4张票,剩余3张票
窗口2卖出了第3张票,剩余2张票
窗口2卖出了第2张票,剩余1张票
窗口2卖出了第1张票,剩余0张票
窗口2余票不足,停止售票!

通过代码执行结果可以看出,在运行的过程中出现了错误。因为窗口总共有30张票,可是窗口1、窗口2、窗口3各自卖出了30张票,等于卖出去90张票,说明车票没有共享。

解决继承Thread类,线程不共享数据的问题

为了解决车票没有共享的问题,我们需要加static关键字,将车票声明为静态变量,让三个售票窗口线程共享车票。

class TicketWindow extends Thread {
    // 车票数量
    private static int ticket = 30;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
            } else {
                System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
                break;
            }
        }
    }
}

public class SellTicketTest1 {
    public static void main(String[] args) {
        TicketWindow tw1 = new TicketWindow();
        TicketWindow tw2 = new TicketWindow();
        TicketWindow tw3 = new TicketWindow();

        tw1.setName("窗口1");
        tw2.setName("窗口2");
        tw3.setName("窗口3");

        tw1.start();
        tw2.start();
        tw3.start();
    }
}

代码执行结果:

窗口1卖出了第30张票,剩余29张票
窗口1卖出了第28张票,剩余27张票
窗口1卖出了第27张票,剩余26张票
窗口1卖出了第26张票,剩余25张票
窗口1卖出了第25张票,剩余24张票
窗口2卖出了第29张票,剩余28张票
窗口2卖出了第22张票,剩余21张票
窗口2卖出了第21张票,剩余20张票
窗口2卖出了第20张票,剩余19张票
窗口2卖出了第19张票,剩余18张票
窗口3卖出了第23张票,剩余22张票
窗口3卖出了第17张票,剩余16张票
窗口3卖出了第16张票,剩余15张票
窗口3卖出了第15张票,剩余14张票
窗口3卖出了第14张票,剩余13张票
窗口3卖出了第13张票,剩余12张票
窗口3卖出了第12张票,剩余11张票
窗口3卖出了第11张票,剩余10张票
窗口3卖出了第10张票,剩余9张票
窗口3卖出了第9张票,剩余8张票
窗口3卖出了第8张票,剩余7张票
窗口3卖出了第7张票,剩余6张票
窗口3卖出了第6张票,剩余5张票
窗口3卖出了第5张票,剩余4张票
窗口3卖出了第4张票,剩余3张票
窗口3卖出了第3张票,剩余2张票
窗口3卖出了第2张票,剩余1张票
窗口3卖出了第1张票,剩余0张票
窗口3余票不足,停止售票!
窗口1卖出了第24张票,剩余23张票
窗口1余票不足,停止售票!
窗口2卖出了第18张票,剩余17张票
窗口2余票不足,停止售票!

2.使用实现Runnable接口的方式

一个类通过继承Thread来实现多线程的话,则不适合多个线程共享资源,而通过实现Runnable就可以做到这一点。

class TicketWindow2 implements Runnable {
    private int ticket = 30;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
            } else {
                System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
                break;
            }
        }
    }
}

public class SellTicketTest2 {
    public static void main(String[] args) {
        TicketWindow2 tw = new TicketWindow2();

        for (int i = 1; i < 4; i++) {
            Thread t = new Thread(tw,"窗口"+i);
            t.start();
        }
    }
}

为了节省篇幅,这儿不再展示测试结果!

第二种方式不需要加static关键字把车票声明为静态的,也实现了数据共享的问题。
实现Runnable接口的方式创建多线程,只产生了一个TicketWindow2对象,一个对象里边有一个属性,这样三个线程同时在操作一个属性,运行同一个run方法。
在实际开发中,推荐使用第二种方式。

以上两种方式存在线程安全问题:

打印车票时,会出现重票和错票。造成这个问题的原因是一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。

如何解决:

当线程1在操作ticket的时候,其他线程不能参与进来。直到线程1操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程1出现了阻塞,也不能被改变。

在Java中,我们通过同步机制,来解决线程的安全问题。

3.解决卖票过程中的线程安全问题

在这篇博客中,使用了同步方法的方式解决了线程安全问题。

代码实现:

class TicketWindow3 implements Runnable{
    private static int ticket = 30;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        // 线程出现阻塞
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket-- + "张票," + "剩余" + ticket + "张票");
                } else {
                    System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
                    break;
                }
            }
        }
    }
}

public class SellTicketTest3 {
    public static void main(String[] args) {
        TicketWindow3 tw = new TicketWindow3();

        for (int i = 1; i < 4; i++) {
            Thread t = new Thread(tw,"窗口"+i);
            t.start();
        }
    }
}

代码执行结果:

窗口1卖出了第30张票,剩余29张票
窗口3卖出了第29张票,剩余28张票
窗口3卖出了第28张票,剩余27张票
窗口2卖出了第27张票,剩余26张票
窗口3卖出了第26张票,剩余25张票
窗口1卖出了第25张票,剩余24张票
窗口1卖出了第24张票,剩余23张票
窗口1卖出了第23张票,剩余22张票
窗口1卖出了第22张票,剩余21张票
窗口1卖出了第21张票,剩余20张票
窗口1卖出了第20张票,剩余19张票
窗口1卖出了第19张票,剩余18张票
窗口3卖出了第18张票,剩余17张票
窗口2卖出了第17张票,剩余16张票
窗口2卖出了第16张票,剩余15张票
窗口2卖出了第15张票,剩余14张票
窗口2卖出了第14张票,剩余13张票
窗口2卖出了第13张票,剩余12张票
窗口2卖出了第12张票,剩余11张票
窗口2卖出了第11张票,剩余10张票
窗口2卖出了第10张票,剩余9张票
窗口2卖出了第9张票,剩余8张票
窗口3卖出了第8张票,剩余7张票
窗口1卖出了第7张票,剩余6张票
窗口3卖出了第6张票,剩余5张票
窗口3卖出了第5张票,剩余4张票
窗口2卖出了第4张票,剩余3张票
窗口3卖出了第3张票,剩余2张票
窗口3卖出了第2张票,剩余1张票
窗口1卖出了第1张票,剩余0张票
窗口1余票不足,停止售票!
窗口3余票不足,停止售票!
窗口2余票不足,停止售票!

上一篇 多线程(二)Java实现生产者和消费者模式

发布了102 篇原创文章 · 获赞 4575 · 访问量 92万+

猜你喜欢

转载自blog.csdn.net/weixin_43570367/article/details/102787154