Java入门:5分钟了解使用多线程实现三个窗口售票

假设有100张火车票,有三个窗口可以卖票,如何使用多线程实现三个窗口抢票?
对于单个窗口,我们会写一个简单的循环语句:
public class Ticket {
    
    
    int tick = 100;

    public void sale() {
    
    
        while(true) {
    
    
            if (tick > 0) {
    
    
                System.out.println("剩余车票: 售出车票号码为:" + tick--);
            }else {
    
    
                break;
            }
        }
    }

    public static void main(String[] args) {
    
    
        Ticket ticket = new Ticket();
        ticket.sale();
    }
}

如果是三个窗口同时开展售票业务,就要使用多线程解决这个问题了。Java实现多线程的步骤如下:

  • 定义接口实现类,实现Runnable接口。
  • 重写Runnable接口中的run方法。
  • 通过Thread类含参构造器创建线程对象。
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  • 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
根据上面的步骤,我们可以很容易写出一个大概的框架:
class Sale implements Runnable{
    
    //定义接口实现类,实现Runnable接口
    @Override
    public void run() {
    
    //子类中重写Runnable接口中的run方法。
        System.out.println("多线程执行语句blablabla");
    }

    public static void main(String[] args){
    
    
    //通过Thread类含参构造器创建线程对象。
    //将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
    //调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
        new Thread(new Sale()).start();
        new Thread(new Sale()).start();
        new Thread(new Sale()).start();
    }
}
-------------------------
多线程执行语句blablabla
多线程执行语句blablabla
多线程执行语句blablabla

我们可以给三个线程命名,并且在显示的时候输出它们的名字:

class Sale implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"多线程执行语句blablabla");
    }

    public static void main(String[] args){
    
    
        new Thread(new Sale(),"1号窗口").start();
        new Thread(new Sale(),"2号窗口").start();
        new Thread(new Sale(),"3号窗口").start();
    }
}
-------------------------
2号窗口多线程执行语句blablabla
1号窗口多线程执行语句blablabla
3号窗口多线程执行语句blablabla

然后将前面写好的语句,放到Sale类里,就是下面这样,运行:

package indi.huishi.method;
class Sale implements Runnable{
    
    
    int tick = 100;
    @Override
    public void run() {
    
    
//        System.out.println(Thread.currentThread().getName()+"多线程执行语句blablabla");
        while(true) {
    
    
            if (tick > 0) {
    
    
                try {
    
    //切换到另外的线程
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"售出车票,售出车票号码为: " + tick--);
            }else {
    
    
                break;
            }
        }
    }

    public static void main(String[] args){
    
    
        new Thread(new Sale(),"1号窗口").start();
        new Thread(new Sale(),"2号窗口").start();
        new Thread(new Sale(),"3号窗口").start();
    }
}
-------------------------
2号窗口售出车票,售出车票号码为: 100
3号窗口售出车票,售出车票号码为: 100
1号窗口售出车票,售出车票号码为: 100
1号窗口售出车票,售出车票号码为: 99
2号窗口售出车票,售出车票号码为: 99
3号窗口售出车票,售出车票号码为: 99
...
3号窗口售出车票,售出车票号码为: 2
1号窗口售出车票,售出车票号码为: 2
2号窗口售出车票,售出车票号码为: 2
1号窗口售出车票,售出车票号码为: 1
2号窗口售出车票,售出车票号码为: 1
3号窗口售出车票,售出车票号码为: 1

发现每个窗口都售出了100张票?这是因为我们new了3个Sale对象,所以每个线程都有各卖各的100张票。而这里的资源(ticket)是三个线程的共享数据,所以应该只new一个Sale对象。

    public static void main(String[] args){
    
    
        Sale sale = new Sale();
        new Thread(sale,"1号窗口").start();
        new Thread(sale,"2号窗口").start();
        new Thread(sale,"3号窗口").start();
    }
1号窗口售出车票,售出车票号码为: 100
3号窗口售出车票,售出车票号码为: 99
2号窗口售出车票,售出车票号码为: 98
...
1号窗口售出车票,售出车票号码为: 8
2号窗口售出车票,售出车票号码为: 6
3号窗口售出车票,售出车票号码为: 7
3号窗口售出车票,售出车票号码为: 5
2号窗口售出车票,售出车票号码为: 4
1号窗口售出车票,售出车票号码为: 3
3号窗口售出车票,售出车票号码为: 1
1号窗口售出车票,售出车票号码为: 2
2号窗口售出车票,售出车票号码为: 0

因为售出车票号码从100开始到1,但是却出现了0号车票。而且,剩余车票应该是每卖出一张就减少1,但是我们发现它的剩余车票并不是递减变化的。
这是由于共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行。比如一个线程比其他两个线程先做了ticket--,而它的输出却比另外两个线程慢。如果同时有多个线程满足ticket>0条件进入if条件下面的语句,如果此时ticket=1,这样就会输出车票0和-1的情况。

解决问题的办法就是:多条操作共享数据的语句,只能让一个线程都执行完在执行过程中,其他线程不可以参与执行,也就是使用同步机制

Java可以使用synchronized关键字实现同步机制。

synchronized关键字

synchronized可以应用于:

  • 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

类的对象可以作为锁,这是synchronized实现同步的基础。所以我们建立一个对象obj作为锁。使用锁的原则是:使用同一个资源的多个线程共用一把锁,所以obj不能在下面的循环体中循环创建。

class Sale implements Runnable{
    
    
    int tick = 100;
    Object obj = new Object();
    @Override
    public void run() {
    
    
        while(true) {
    
    
            if (tick > 0) {
    
    
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"售出车票,售出车票号码为: " + tick--);
            }else {
    
    
                break;
            }
        }
    }

前面提到在同步范围内,对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。这样就不会出现票号码的乱序,或者号码为0或者-1的情况。
我们应该需要确定同步的范围。既不能太小,没锁住所有有安全问题的代码,也不能太大,没发挥多线程的功能。原则是:所有操作共享数据的这些语句都要放在同步范围中

如果synchronizedif里面,起不到同步的效果,因为tick>0操作共享数据的语句,必须要放在同步范围中。
如果synchronizedwhere外面,那么第一个线程开始执行,等到执行完,就是tick=0的情况,那么这个窗口卖完了所有的票。其他两个线程开始执行的时候,tick=0,已经没有票了。
所以synchronized应该在ifwhere中间,while每循环一次,会有一个线程来执行里面 操作共享数据的多条语句(if语句)。完整代码如下:

package indi.huishi.method;
class Sale implements Runnable{
    
    
    int tick = 100;
    Object obj = new Object();
    @Override
    public void run() {
    
    
        while(true) {
    
    
            synchronized (obj) {
    
    
                if (tick > 0) {
    
    
                    try {
    
    
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "售出车票,售出车票号码为: " + tick--);
                }else {
    
    
                    break;
                }

            }
        }
    }

    public static void main(String[] args){
    
    
        Sale sale = new Sale();
        new Thread(sale,"1号窗口").start();
        new Thread(sale,"2号窗口").start();
        new Thread(sale,"3号窗口").start();
    }
}
1号窗口售出车票,售出车票号码为: 100
1号窗口售出车票,售出车票号码为: 99
1号窗口售出车票,售出车票号码为: 98
1号窗口售出车票,售出车票号码为: 97
1号窗口售出车票,售出车票号码为: 96
1号窗口售出车票,售出车票号码为: 95
1号窗口售出车票,售出车票号码为: 94
1号窗口售出车票,售出车票号码为: 93
...
2号窗口售出车票,售出车票号码为: 7
2号窗口售出车票,售出车票号码为: 6
2号窗口售出车票,售出车票号码为: 5
2号窗口售出车票,售出车票号码为: 4
2号窗口售出车票,售出车票号码为: 3
2号窗口售出车票,售出车票号码为: 2
2号窗口售出车票,售出车票号码为: 1

猜你喜欢

转载自blog.csdn.net/qq_36937684/article/details/115220146