引言
现有一个需求如下:
有10000张火车票,每张票都有一个编号,同时有10个窗口对外售票,如何确保车票的正常售卖?
程序一:使用List
问题的解决办法都是从我们最最熟悉的角度思考。程序一,我们使用一个普通的List作为方案。
阅读以下代码,观察执行结果:
public class TicketSell_01 {static List<String> tickets = new ArrayList<>();static {for (int i = 0; i < 10000; i++)tickets.add("票编号: " + i);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> {while (tickets.size() > 0) {System.out.println("销售了--" + tickets.remove(0));}}).start();}}
}
输出结果如下,可以看到,编号0的车票被销售了两次。List不是同步容器,容器内的remove()等方法都无法做到原子性,因此会出现重复售票的问题,因此是不安全的:
程序二:使用Vector
以Vector代替List作为容器。区别是,Vector是同步容器,内部的方法都是同步的。但是下面的代码依然会存在问题。
虽然size()方法和remove()方法本身是原子性的,其他线程无法打断,但是在判断size和remove之间的部分依然会有线程交叉执行的可能,这样,虽然可以解决重复销售的问题,但是依然会导致:ArrayIndexOutOfBoundsException
public class TicketSell_02 {static Vector<String> tickets = new Vector<>();static {for (int i = 0; i < 10000; i++)tickets.add("票编号: " + i);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> {while (tickets.size() > 0) {try {TimeUnit.MILLISECONDS.sleep(5);} catch (Exception e) {e.printStackTrace();}System.out.println("销售了--" + tickets.remove(0));}}).start();}}
}
执行结果:
程序三:同步
使用synchronized进行线程同步,可以有效解决逻辑问题,但是很明显,这种方法的缺点就是效率低下。
public class TicketSell_03 {static List<String> tickets = new LinkedList<>();static {for (int i = 0; i < 10000; i++)tickets.add("票编号: " + i);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> {while (true) {synchronized (tickets) {if (tickets.size() == 0)break;System.out.println("销售了票--" + tickets.remove(0));}}}).start();}}
}
程序四:并发容器Queue
ConcurrentLinkedQueue是一个并发队列。但凡并发容器,其内部的方法都保证是原子性的。下面的代码中poll()表示从队列的头部获得一个数据,当返回值为null时,代表这个队列已经没有值了。因为队列本身不允许存null值,否则会报空指针异常,因此当返回值为null时,一定表示队列已空(size() == 0)。队列的底层是使用一个叫做CompareAndSet(CAS)的技术实现的,不是加锁的实现,因此在高并发的情况下依然可以拥有很高的效率。
public class TicketSell_04 {static Queue<String> tickets = new ConcurrentLinkedQueue<>();static {for (int i = 0; i < 10000; i++)tickets.add("票编号: " + i);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> {while (true) {String s = tickets.poll();if (s == null)break;elseSystem.out.println("销售了--" + s);}}).start();}}
}