3个售票员卖30张票问题
Ticket
package com.mervyn.ticketvendingmachine.domain;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Ticket {
private String name;
public Ticket(String name) {
this.name = name;
}
}
ResourcePool
package com.mervyn.ticketvendingmachine.domain;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
@Setter
@Getter
public class ResourcePool {
private List<Ticket> tickets;
public ResourcePool(int ticketNum) {
tickets = new ArrayList<>();
IntStream.range(0,ticketNum).forEach(i -> tickets.add(new Ticket(String.valueOf(i+1))));
}
public void sell(){
if(CollectionUtils.isNotEmpty(tickets)){
Ticket ticket = tickets.get(tickets.size()-1);
System.out.println(Thread.currentThread().getName() + "卖掉了第" + ticket.getName() + "张票");
tickets.remove(ticket);
}
}
}
CustomThreadFactory
package com.mervyn.ticketvendingmachine.domain;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class CustomThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public CustomThreadFactory() {
namePrefix = "线程";
}
public Thread newThread(Runnable r) {
return new Thread(r, namePrefix + threadNumber.getAndIncrement());
}
}
自定义线程的名字,主要为了让打印效果更明显。
单元测试类ResourcePoolTest
package com.mervyn.ticketvendingmachine;
import com.mervyn.ticketvendingmachine.domain.CustomThreadFactory;
import com.mervyn.ticketvendingmachine.domain.ResourcePool;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.*;
import java.util.stream.IntStream;
@SpringBootTest
class ResourcePoolTest {
private static ResourcePool resourcePool;
@BeforeAll
static void init(){
resourcePool = new ResourcePool(30);
}
@Test
void test() throws InterruptedException {
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(3, 3,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(), new CustomThreadFactory());
IntStream.range(0,30).forEach( i -> threadPoolExecutor.submit(() -> resourcePool.sell()));
Thread.sleep(1000*10);
}
}
使用3个线程同时获取ResourcePool中的票。
可能的输出
线程1卖掉了第30张票
线程3卖掉了第30张票
线程1卖掉了第29张票
线程2卖掉了第30张票
线程2卖掉了第28张票
线程2卖掉了第27张票
线程1卖掉了第28张票
线程1卖掉了第26张票
线程1卖掉了第25张票
线程1卖掉了第24张票
线程1卖掉了第23张票
线程3卖掉了第29张票
线程3卖掉了第22张票
线程3卖掉了第21张票
线程1卖掉了第22张票
线程1卖掉了第20张票
线程1卖掉了第19张票
线程2卖掉了第26张票
线程2卖掉了第18张票
线程1卖掉了第18张票
线程1卖掉了第17张票
线程3卖掉了第20张票
线程3卖掉了第16张票
线程1卖掉了第16张票
线程2卖掉了第17张票
线程2卖掉了第15张票
线程1卖掉了第15张票
线程3卖掉了第15张票
线程1卖掉了第14张票
线程2卖掉了第14张票
从输出结果中可以看出,存在票被重复售卖的问题。
使用同步修改ResourcePool的sell方法
package com.mervyn.ticketvendingmachine.domain;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
@Setter
@Getter
public class ResourcePool {
private List<Ticket> tickets;
public ResourcePool(int ticketNum) {
tickets = new ArrayList<>();
IntStream.range(0,ticketNum).forEach(i -> tickets.add(new Ticket(String.valueOf(i+1))));
}
public synchronized void sell(){
if(CollectionUtils.isNotEmpty(tickets)){
Ticket ticket = tickets.get(tickets.size()-1);
System.out.println(Thread.currentThread().getName() + "卖掉了第" + ticket.getName() + "张票");
tickets.remove(ticket);
}
}
}
将ResourcePool使用synchronized修饰sell方法后,将使多个线程串行售票。
结果
线程1卖掉了第30张票
线程1卖掉了第29张票
线程1卖掉了第28张票
线程3卖掉了第27张票
线程3卖掉了第26张票
线程2卖掉了第25张票
线程3卖掉了第24张票
线程3卖掉了第23张票
线程3卖掉了第22张票
线程1卖掉了第21张票
线程3卖掉了第20张票
线程3卖掉了第19张票
线程3卖掉了第18张票
线程3卖掉了第17张票
线程3卖掉了第16张票
线程3卖掉了第15张票
线程3卖掉了第14张票
线程2卖掉了第13张票
线程2卖掉了第12张票
线程2卖掉了第11张票
线程3卖掉了第10张票
线程3卖掉了第9张票
线程3卖掉了第8张票
线程3卖掉了第7张票
线程1卖掉了第6张票
线程3卖掉了第5张票
线程2卖掉了第4张票
线程3卖掉了第3张票
线程1卖掉了第2张票
线程2卖掉了第1张票
终于出现期望的效果。