Tomcat运行Web项目,Action中实现资源同步

举例说明:火车站卖票,火车票总数是一定的,卖火车票的窗口有多个(2个),每个窗口就相当于一个线程,这么多的线程共用所有的火车票数量资源。如果在一个时间点上,两个线程同时使用这个资源,这样就会给乘客造成问题。问题1卖出去同一个座位的票;问题2卖出了超出火车票的总数。

在说明上述问题之前,我们首先普及一下,容器(tomcat)并行处理与Servlet的关系;假设12306看作一个web工程,部署在tomcat下。

@Component
public class TicketActionImpl implements TicketAction{
    @Autowired
    TicketService ticketService;
    @Override
    public Response sell(String id) throws InterruptedException {
        System.out.printf("ticketNo="+id+"; print hashcode "+this.hashCode());
        int total = ticketService.getTotal(id);
        ticketService.updateTotal(id,total--);
        total = ticketService.getTotal(id);
        return Response.ok().build();
    }
}
 
@Service
public class TicketServiceImpl implements TicketService{
    public static int total=10;
    public int getTotal(String ticketNo){
        return total;
    }
    @Override
    public void updateTotal(String ticketNo, int count) {
        total=count;
    }
}
发送两次请求
第一次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户1购买G2008火车票)
第二次请求:http://localhost:8080/app/api/ticket?id=G2009(窗户2购买G2009火车票)
控制台打印结果:
	ticketNo=G2008; print hashcode 2049737772
	ticketNo=G2009; print hashcode 2049737772
总结:通过打印TicketActionImpl实例的hashcode,我们发现两次请求hashcode码相同,说明tomcat将请求转发给同一个Selvlet(action)实例来处理,
      并不是每次请求都会新建一个Selvlet(action)实例。
      tomcat容器如何实现并行处理, 各种Web容器,如Tomcat,Resion,Jetty等都有自己的线程池,所以在客户端进行请求调用的时候,程序员不用针
      对Client的每一次请求,都新建一个线程。而容器会自动分配线程池中的线程将用户请求分发给Selvet处理,提高访问速度。

在了解Tomcat与Selvlet关系之后,我们来看看,多线程访问同一资源带来的问题。
 
把action中的sell方法添加Thread.sleep(10000);
public Response sell(String id) throws InterruptedException {
    int total = ticketService.getTotal(id);
    System.out.println("购买前,车票剩余总数:"+total);
    Thread.sleep(10000);
    ticketService.updateTotal(id,--total);
    total = ticketService.getTotal(id);
    System.out.println("购买后,车票剩余总数:"+total);
    return Response.ok().build();
}
发送两次请求
第一次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户1购买G2008火车票)
第二次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户2购买G2008火车票)
控制台打印结果:
	购买前,车票剩余总数:10
	购买前,车票剩余总数:10
	购买后,车票剩余总数:9
	购买后,车票剩余总数:9
总结:通过控制台打印结果发现,两个窗口同时卖同一列次火车票后,火车票总是变成了9张,很明显的错误应该是8张才对,换句话说两个窗口卖出去同一座位的车票。
      (上述例子中Thread.sleep(10000)为了模拟一个耗时较长的操作,例如读写文件、请求其他接口等等)

如何解决在多线程访问同一资源带来的问题,我们可以采取的措施下面详细说明并对比,我们把这种编程称之为同步、加锁。

方法一:在sell方法前声明synchronize关键字。
@Override
public synchronized Response sell(String id) throws InterruptedException {
    System.out.println("进入sell方法");
    Thread.sleep(5000);
int total = ticketService.getTotal(id); System.out.println("购买前,车票剩余总数:"+total); Thread.sleep(10000); ticketService.updateTotal(id,--total); total = ticketService.getTotal(id); System.out.println("购买后,车票剩余总数:"+total);
    System.out.println("跳出sell方法");
return Response.ok().build();
}
发送两次请求
第一次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户1购买G2008火车票)
第二次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户2购买G2008火车票)
控制台打印结果:	
进入sell方法
购买前,车票剩余总数:10
购买后,车票剩余总数:9
跳出sell方法
进入sell方法
购买前,车票剩余总数:9
购买后,车票剩余总数:8
跳出sell方法
总结:通过控制台打印结果发现,两个窗口同时卖同一列次火车票后,第一个窗口卖出后,车票剩余9张;第二个窗口卖出后,车票剩余8张,得到正确结果。
      需要注意的一点:第二个窗口是等第一个窗口卖完票后才会去卖票,也就是说第二个线程会等待第一个线程执行完sell方法后才会执行sell方法,
      内部原理当一个线程调用一个同步方法的时候,他就自动地获得了该方法所属对象的内部锁,并在方法返回的时候释放该锁。
方法二:在sell方法中代码块声明synchronize关键字。
@Override
public Response sell(String id) throws InterruptedException {
    System.out.println("进入sell方法");
    Thread.sleep(5000);
    synchronized(this){
        int total = ticketService.getTotal(id);
        System.out.println("购买前,车票剩余总数:"+total);
        Thread.sleep(10000);
        ticketService.updateTotal(id,--total);
        total = ticketService.getTotal(id);
        System.out.println("购买后,车票剩余总数:"+total);
    }
    System.out.println("跳出sell方法");
    return Response.ok().build();
}
发送两次请求
第一次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户1购买G2008火车票)
第二次请求:http://localhost:8080/app/api/ticket?id=G2008(窗户2购买G2008火车票)
控制台打印结果:
	进入sell方法
	进入sell方法
	购买前,车票剩余总数:10
	购买后,车票剩余总数:9
	跳出sell方法
	购买前,车票剩余总数:9
	购买后,车票剩余总数:8
	跳出sell方法
总结:通过控制台打印结果发现,窗口2没有等待窗口1执行完sell方法后才会去执行sell方法,synchronized(this){}同步代码块持有的也是对象锁。
      对比方法1方法2得出,同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,同步方法锁的范围比较大,而同步代码块范围要小点,
      一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值