赞
踩
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(); } }
运行结果:
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(); } }
运行结果:
每次运行的结果都可能不一样,而且有时候一个线程运行完成了,另一个线程才刚开始运行,有时候交替运行。
但是对于这个程序我有点问题,我觉得对于判断什么时候跑步结束的条件,这个程序是用了一个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;
}
}
}
但是上面程序的运行结果如下:
两个线程同时运行没错,但是每次都会出现两个胜利者的情况,这显然不对。
分析原因:线程开启后,两个线程同时运行,两个线程的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(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。