模拟订票场景,总共10张票,但是有20个人想要购买,所以定义了一个订票管理类专门负责操作这10张票,当然,操作票的时候使用到了synchronized,另外定义了一个调用订票方法的线程类用于模拟想要买票的人,然后创建20个线程模拟20个人来订票。但是运行测试的结果却并没有成功模拟这10张票的售卖过程,原因和处理分析如下:
设置 10 张票出售。
package com.example.study; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 订票管理 * * @author 李关钦 * @date 2017年6月7日 */ public class TicketManager { public static int ticket = 10;// 默认总共 10 张票 private static final Logger logger = LoggerFactory.getLogger(TicketManager.class); /** * 售票,先检查是否有票,有则对原票数减一 * * @return */ public synchronized boolean buyTicket() { // 延迟 1 秒钟,模拟实际情况中的处理时间 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0) { logger.info("current thread name is [{}] , before is [{}] , after is [{}]", Thread.currentThread().getName(), ticket, --ticket); return true; } else { logger.info("current thread name is [{}] , ticket is [{}] , The ticket has been sold out !!!", Thread.currentThread().getName(), ticket); return false; } } }模拟订票的人。
package com.example.study; /** * 订票线程,实现 Runnable 接口 * * @author 李关钦 * @date 2017年6月7日 */ public class BuyTicketRunnable implements Runnable { private TicketManager ticketManager = new TicketManager(); @Override public void run() { ticketManager.buyTicket(); } }创建 20 个线程,模拟
package com.example.study; /** * * * @author 李关钦 * @date 2017年6月7日 */ public class Test { public static void main(String[] args) { // 设置 20 个进程,模拟 20 个人订票 for (int i = 0; i < 20; i++) { Thread thread = new Thread(new BuyTicketRunnable()); thread.start(); } } }结果:
从运行结果可以看到,线程 10 和线程 8 订票的结果一样了,也就说明了使用 synchronized 修饰的订票方法不起效,达不到互斥的效果。
原因: 在以上代码中,每创建一个订票线程 BuyTicketRunnabl创建一个 TicketManager 订票管理对象与之对应,也就是说创建了 20 个订票线程就会有 20 个对应的订票管理对象去操作票数,所以达不到互斥的关系。
解决方法: 在订票线程中用 static 修饰订票管理对象,使订票管理对象成为全局变量,与其他线程共享,所以 创建的 20 个线程就会共享同一个 TicketManager 订票管理对象,而 TicketManager 里面的订票方法又是使用 synchronized 的,所以就会达到互斥的效果。
package com.example.study; /** * 订票线程,实现 Runnable 接口 * * @author 李关钦 * @date 2017年6月7日 */ public class BuyTicketRunnable implements Runnable { private static TicketManager ticketManager = new TicketManager(); @Override public void run() { ticketManager.buyTicket(); } }