模拟多线程并发购票系统编程
1、数据表
CREATE TABLE `t_ticket` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '表id',
`ticket_count` int(10) NOT NULL DEFAULT '0' COMMENT '票数量',
`type` varchar(15) DEFAULT NULL COMMENT '票类型',
`version` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`),
KEY `index_type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `t_ticket` (`id`, `ticket_count`, `type`, `version`) VALUES ('1', '200', 'movie', '40');
2、售票扣减票数sql
先执行for update锁住数据库操作的行,再执行更新操作,更新操作加版本号
<select id="selectByTypeForUpdate" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List"/>
from t_ticket
where type = #{type,jdbcType=VARCHAR} for update
</select>
<update id="updateTicketSale" parameterType="com.example.ticket.demo.model.table.Ticket">
update t_ticket
<set>
version = ${version} + 1,
<if test="ticketCount != null">
ticket_count = #{ticketCount,jdbcType=INTEGER}
</if>
</set>
where ticket_count > 0 and version = #{version,jdbcType=INTEGER} and id = #{id,jdbcType=INTEGER}
</update>
3、数据库操作业务代码
@Transactional
@Override
public int updateTicketSale(Ticket record) {
Ticket t = ticketMapper.selectByTypeForUpdate(record.getType());
if (t.getTicketCount() > 0) {
record.setVersion(t.getVersion());
return ticketMapper.updateTicketSale(record);
}
return 0;
}
4、售票扣减数量代码
@Slf4j
public class TicketSaleManager {
private static ITicketService ticketService;
//要确保tickCount只有一个实例
public static volatile int tickCount;
public TicketSaleManager() {
if (ticketService == null){
ticketService = SpringUtils.getBean("ticketService");
}
}
public void saleTicket(String salerName, int saleCount) {
synchronized (TicketSaleManager.class) {
Ticket ticket = ticketService.selectByType("movie");
tickCount = ticket.getTicketCount();
try {
if (tickCount > 0) {
if (saleCount > tickCount) {
saleCount = tickCount;
}
log.info(salerName + ": 售出" + saleCount + "张票。");
tickCount = tickCount - saleCount;
Ticket record = new Ticket();
record.setId(ticket.getId());
record.setTicketCount(tickCount);
record.setType("movie");
ticketService.updateTicketSale(record);
log.info(">>>剩下" + tickCount + "张票。");
} else {
log.info(">>>票已被售完。");
}
} catch (Exception e) {
log.error("sale Exception", e);
}
}
}
}
5、创建执行售票线程
class TicketSaleThread implements Runnable {
private String salerName;// 售票员姓名
private int saleCount;//售票数量
public TicketSaleThread(String salerName, int saleCount) {
this.salerName = salerName;
this.saleCount = saleCount;
}
@Override
public void run() {
new TicketSaleManager().saleTicket(salerName, saleCount);
}
}
6、模拟多窗口多线程售票
@Test
public void test() throws InterruptedException {
int windowCount = 6;//售票窗口数量
ExecutorService executorService = Executors.newFixedThreadPool(windowCount);
for (int i = 0; i < 10000; i++) {
windowCount = windowCount < 1? 4: windowCount;
int saleCount = (int) (Math.random() * 10.0d);//saleCount售票数量
saleCount = saleCount < 1 ? 1 : saleCount;
TicketSaleThread threadDemo = new TicketSaleThread(">>>售票窗口:" + windowCount, saleCount);
executorService.execute(threadDemo);
windowCount--;
}
Thread.sleep(10000L);
executorService.shutdown();
}