17Java的多线程抢票程序(小改)

多线程

模拟抢票的程序
package ThreadStudy;

/**模拟抢票功能
 * 抢票需要注意的是多个线程来抢夺同一份资源。一份资源,多个代理。
 *
 * @author 发达的范
 * @version 1.0
 * @date 2021/04/17 14:39
 */
public class Web12306 implements Runnable {
    private int ticketNumbers = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNumbers <= 0) {
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (Thread.currentThread().getName() == "Agent2") {
                System.out.println("           " + Thread.currentThread().getName() + "->" + ticketNumbers--);
            } else if (Thread.currentThread().getName() == "Agent3") {
                System.out.println("                      " + Thread.currentThread().getName() + "->" + ticketNumbers--);
            } else {
                System.out.println(Thread.currentThread().getName() + "->" + ticketNumbers--);
            }
        }
    }

    public static void main(String[] args) {
        //一份资源
        Web12306 web1 = new Web12306();
        System.out.println(Thread.currentThread().getName());
        //多个代理
        new Thread(web1, "Agent1").start();
        new Thread(web1, "Agent2").start();
        new Thread(web1, "Agent3").start();
    }
}

运行结果:17Java的多线程抢票程序(小改)_第1张图片

run方法里面让线程休眠200ms是为了让程序运行的过程可视化,不然执行的太快就看不到多线程的效果了。

从运行结果可以看到,首先输出的是主线程main的线程名称,然后这里使用的是创建一个线程类Web12306类的对象,使用这个对象开启了三个线程,这三个线程访问的是同一个类的对象的资源,也就是**“一份资源,多个代理”**,但是我们可以看到,Agent3=0的时候票已经卖完了,本应该结束卖票,但是Agent1仍然访问到了ticketNumbers并且做了减一操作,这显然是不正常的,为什么?如何避免这种现象?

下面是模拟龟兔赛跑的程序
package ThreadStudy;

/**模拟龟兔赛跑
 * 龟兔赛跑跟抢票不同,抢票是不同的代理访问/修改同一份资源,龟兔赛跑是两个对象(龟和兔)拥有各自的计步器,
 * 但是拥有共同的属性:获胜者(winner),因为只能有一个获胜者,所以两个对象都能够访问/修改同一份资源(winner),
 * 也就是说龟兔赛跑与抢票的相同点就是他们都有公共的属性。
 *
 * @author 发达的范
 * @version 1.0
 * @date 2021/04/17 16:30
 */
public class RabbitRace implements Runnable {
    private String winner;

    @Override
    public void run() {
        for (int steps = 1; steps <= 10; steps++) {
            System.out.println(Thread.currentThread().getName() + "->" + steps);
            boolean flag = gameOver(steps);
            if (flag) {
                break;
            }
        }
    }

    public boolean gameOver(int steps) {
        if (winner != null) {//这里用的是反过来的思想,这个思想在Java编程中很常见,首先判断条件不成立,然后再判断条                               //成件立该怎么做
            return true;
        } else {
            if (steps == 10) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        RabbitRace race = new RabbitRace();
        new Thread(race,"tortoise").start();//注意,这里是开启了两个线程,虽然他们共用一个winner变量
        new Thread(race,"rabbit").start();
    }
}

运行结果:17Java的多线程抢票程序(小改)_第2张图片

每次运行的结果都可能不一样,而且有时候一个线程运行完成了,另一个线程才刚开始运行,有时候交替运行。

但是对于这个程序我有点问题,我觉得对于判断什么时候跑步结束的条件,这个程序是用了一个gameOver方法,我认为不好,可以直接在run方法中写成这样:

    @Override
    public void run() {
        for (int steps = 1; steps <= 10; steps++) {
            System.out.println(Thread.currentThread().getName() + "->" + steps);
            if (steps == 10) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is "+winner);
                break;
            }
        }
    }

但是上面程序的运行结果如下:17Java的多线程抢票程序(小改)_第3张图片

两个线程同时运行没错,但是每次都会出现两个胜利者的情况,这显然不对。

分析原因:线程开启后,两个线程同时运行,两个线程的run方法中的for循环都开始循环输出,虽然都在for循环中设置了循环中止条件,但是这个胜利者中止条件是**“线程独立的”**,姑且就这样叫,意思是两个线程的胜利者终止条件是相互独立的,也就是说一个线程条件满足并break不影响另一个线程结束,tortoise胜利了给winner赋值之后,rabbit仍然可以给winner赋值,问题就出在这里,还有一个问题就是,for循环本来就10个,if条件语句的终止条件也是10所以说这个if语句没用,肯定会输出。

假如写成一个返回值为布尔值的方法,在方法中判断胜利者条件,并且给winner赋值,所以只有其中一个线程满足了终止条件,这个线程就停止运行,返回并显示winner,同时另一个线程同样终止,对于这个现象我理解的是这两个线程是来源于同一个线程类的对象,其中一个把run方法关闭了,另一个也就随之停止了

上面这个解释不对,一个线程赢了另一个线程也随之停止的原因是,赢的那个线程把共同的参考变量属性winner赋值使之非空,所以另一个线程在判断是否有胜利者的时候发现winner非空,所以这个线程就停止了,而且输出操作可能已经做完了,所以会出现winner已经出现,另一个线程还输出了一个值。

每个线程每跑一步就会判断一次有没有产生胜利者,所以如果已经有胜利者,另一个也就不会再跑了。

哦!我明白了,我知道出现两个胜利者是是因为这种写法有bug,这段代码拿下来:

@Override
public void run() {
    for (int steps = 1; steps <= 10; steps++) {
        System.out.println(Thread.currentThread().getName() + "->" + steps);
        if (steps == 10) {
            winner = Thread.currentThread().getName();
            System.out.println("Winner is "+winner);
            break;
        }
    }
}

if (steps == 10)条件只是判断步数steps是否达到总数,达到总数就break循环,关键是两个线程都能够达到总数,换句话说,有没有这个if语句,输出结果都一样,都会出现两个winner,所以说这两个线程缺少一个统一的暂停标准,而使用gameOver方法,这个方法里面的这段代码:if (winner != null) { return true; } 就是提供了这个统一的标准。

if (winner != null) { return true; },winner不等于null说明此时已经有了胜利者,直接返回true,终止程序,这一句很关键,如果其中一个线程胜利了,那么winner就不是null, 所以直接停止运行,同时停止另外一个线程,因为这两个线程共享的一个对象的winner属性

所以,如果不用gameOver方法,那么run方法可以写成如下形式,运行结果正确:

@Override
    public void run() {
        for (int steps = 1; steps <= 10; steps++) {
            System.out.println(Thread.currentThread().getName() + "->" + steps);
            if (winner != null) {
                break;
            } else {
                if (steps == 10) {
                    winner = Thread.currentThread().getName();
                    System.out.println("Winner is " + winner);
                    break;
                }
            }
        }
    }

如果兔子每跑10步就休息一下,那么程序改为:

package ThreadStudy;

/**
 * 模拟龟兔赛跑
 *
 * @author 发达的范
 * @version 1.0
 * @date 2021/04/17 16:30
 */
public class RabbitRace implements Runnable {
    private String winner;

    @Override
    public void run() {
        for (int steps = 1; steps <= 100; steps++) {
            System.out.println(Thread.currentThread().getName() + "->" + steps);
            if (Thread.currentThread().getName().equals("rabbit")&&steps%10==0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (winner != null) {
                break;
            } else {
                if (steps == 100) {
                    winner = Thread.currentThread().getName();
                    System.out.println("Winner is " + winner);
                    break;
                }
            }
//            boolean flag = gameOver(steps);
//            if (flag) {
//                break;
//            }
        }
    }

    public boolean gameOver(int steps) {
        if (winner != null) {//winner不等于null说明此时已经有了胜利者,直接返回true,终止程序,这一句很关键,如果其中一个线程胜利了,那么winner就不是null,
            // 所以直接停止运行,同时停止另外一个线程,因为这两个线程共享的一个对象的winner属性
            return true;
        } else {//如果此时没有胜利者,那么就进行判断是否跑完全程
            if (steps == 100) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is " + winner);
                return true;
            }
            return false;
        }
    }

    public static void main(String[] args) {
        RabbitRace race = new RabbitRace();
        new Thread(race, "tortoise").start();
        new Thread(race, "rabbit").start();
    }
}

你可能感兴趣的:(学习JavaSE第二阶段,java,多线程)