之前用java多线程模拟售票系统时,曾使用Integer来代表总数并作为同步对象:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class SellTicket {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//方法1,创建Thread子类进行提交,同步控制失败
Integer tickets = Integer.valueOf(10);
TicketWindow tw = new TicketWindow(tickets);
for(int i=1; i<4; i++){
FutureTask<Void> ft = new FutureTask<Void>(tw);
Thread t = new Thread(ft,"TickWindow-" + i);
t.start();
}
//方法2,创建线程池提交,会出现三个窗口各自卖10张票
// Integer tickets = Integer.valueOf(10);
// ExecutorService executorService = Executors.newFixedThreadPool(3);
// for(int i=1; i<4; i++){
// TicketWindow tw = new TicketWindow(tickets);
// Future<Void> future = executorService.submit(tw);
// }
// executorService.shutdown();
}
}
class TicketWindow implements Callable<Void>{
//private int tickets = 10;//车票总量
private Integer tickets;
public TicketWindow(Integer tickets){
this.tickets = tickets;
}
@Override
public Void call(){
while(true){
synchronized (tickets) {
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "准备出票,剩余票数:" + tickets + "张");
--tickets;
System.out.println(Thread.currentThread().getName() + "卖出一张,剩余票数:" + tickets + "张");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
System.out.println(Thread.currentThread().getName() + "余票不足,停止售票!");
break;
}
}
}
return null;
}
}
方法一是会出现同步失败的:
TickWindow-1准备出票,剩余票数:10张
TickWindow-1卖出一张,剩余票数:9张
TickWindow-2准备出票,剩余票数:9张
TickWindow-2卖出一张,剩余票数:8张
TickWindow-3准备出票,剩余票数:8张
TickWindow-3卖出一张,剩余票数:7张
TickWindow-1准备出票,剩余票数:7张
TickWindow-1卖出一张,剩余票数:6张
TickWindow-1准备出票,剩余票数:6张
TickWindow-1卖出一张,剩余票数:5张
TickWindow-2准备出票,剩余票数:5张
TickWindow-2卖出一张,剩余票数:4张
TickWindow-3准备出票,剩余票数:4张
TickWindow-3卖出一张,剩余票数:3张
TickWindow-1准备出票,剩余票数:3张
TickWindow-1卖出一张,剩余票数:2张
TickWindow-3准备出票,剩余票数:2张
TickWindow-3卖出一张,剩余票数:1张
TickWindow-1准备出票,剩余票数:1张
TickWindow-2准备出票,剩余票数:1张
TickWindow-2卖出一张,剩余票数:-1张
TickWindow-1卖出一张,剩余票数:0张
TickWindow-3余票不足,停止售票!
TickWindow-1余票不足,停止售票!
TickWindow-2余票不足,停止售票!
方法二则会直接是3个窗口各自卖10张票:
pool-1-thread-1准备出票,剩余票数:10张
pool-1-thread-1卖出一张,剩余票数:9张
pool-1-thread-1准备出票,剩余票数:9张
pool-1-thread-1卖出一张,剩余票数:8张
pool-1-thread-3准备出票,剩余票数:10张
pool-1-thread-3卖出一张,剩余票数:9张
pool-1-thread-1准备出票,剩余票数:8张
pool-1-thread-1卖出一张,剩余票数:7张
pool-1-thread-3准备出票,剩余票数:9张
pool-1-thread-3卖出一张,剩余票数:8张
pool-1-thread-2准备出票,剩余票数:10张
pool-1-thread-2卖出一张,剩余票数:9张
pool-1-thread-3准备出票,剩余票数:8张
pool-1-thread-3卖出一张,剩余票数:7张
pool-1-thread-1准备出票,剩余票数:7张
pool-1-thread-1卖出一张,剩余票数:6张
pool-1-thread-2准备出票,剩余票数:9张
pool-1-thread-2卖出一张,剩余票数:8张
pool-1-thread-1准备出票,剩余票数:6张
pool-1-thread-1卖出一张,剩余票数:5张
pool-1-thread-3准备出票,剩余票数:7张
pool-1-thread-3卖出一张,剩余票数:6张
pool-1-thread-2准备出票,剩余票数:8张
pool-1-thread-2卖出一张,剩余票数:7张
pool-1-thread-1准备出票,剩余票数:5张
pool-1-thread-3准备出票,剩余票数:6张
pool-1-thread-1卖出一张,剩余票数:4张
pool-1-thread-3卖出一张,剩余票数:5张
pool-1-thread-2准备出票,剩余票数:7张
pool-1-thread-2卖出一张,剩余票数:6张
pool-1-thread-3准备出票,剩余票数:5张
pool-1-thread-3卖出一张,剩余票数:4张
pool-1-thread-1准备出票,剩余票数:4张
pool-1-thread-1卖出一张,剩余票数:3张
pool-1-thread-2准备出票,剩余票数:6张
pool-1-thread-2卖出一张,剩余票数:5张
pool-1-thread-1准备出票,剩余票数:3张
pool-1-thread-3准备出票,剩余票数:4张
pool-1-thread-3卖出一张,剩余票数:3张
pool-1-thread-1卖出一张,剩余票数:2张
pool-1-thread-2准备出票,剩余票数:5张
pool-1-thread-2卖出一张,剩余票数:4张
pool-1-thread-1准备出票,剩余票数:2张
pool-1-thread-1卖出一张,剩余票数:1张
pool-1-thread-3准备出票,剩余票数:3张
pool-1-thread-3卖出一张,剩余票数:2张
pool-1-thread-2准备出票,剩余票数:4张
pool-1-thread-2卖出一张,剩余票数:3张
pool-1-thread-1准备出票,剩余票数:1张
pool-1-thread-1卖出一张,剩余票数:0张
pool-1-thread-3准备出票,剩余票数:2张
pool-1-thread-3卖出一张,剩余票数:1张
pool-1-thread-2准备出票,剩余票数:3张
pool-1-thread-2卖出一张,剩余票数:2张
pool-1-thread-1余票不足,停止售票!
pool-1-thread-3准备出票,剩余票数:1张
pool-1-thread-3卖出一张,剩余票数:0张
pool-1-thread-2准备出票,剩余票数:2张
pool-1-thread-2卖出一张,剩余票数:1张
pool-1-thread-3余票不足,停止售票!
pool-1-thread-2准备出票,剩余票数:1张
pool-1-thread-2卖出一张,剩余票数:0张
pool-1-thread-2余票不足,停止售票!
之所以出现这样的情况,是因为Integer的自动拆箱装箱特性:
synchronized (tickets) {
--tickets;
}
其实相当于:
synchronized (tickets) {
tickets=new Integer(--tickets);
}
tickets已经是一个新的Integer对象了.
解决办法:可使用线程安全的AtomicInteger
因此:其他基本类型的封装类型,如Short,Long,Double,Float等,也不能作为同步对象.
参考: