当前位置:   article > 正文

Java并发编程实战————售票问题_java售票并发问题

java售票并发问题

引言

现有一个需求如下:

有10000张火车票,每张票都有一个编号,同时有10个窗口对外售票,如何确保车票的正常售卖?

程序一:使用List

问题的解决办法都是从我们最最熟悉的角度思考。程序一,我们使用一个普通的List作为方案。

阅读以下代码,观察执行结果:

  1. public class TicketSell_01 {
  2. static List<String> tickets = new ArrayList<>();
  3. static {
  4. for (int i = 0; i < 10000; i++)
  5. tickets.add("票编号: " + i);
  6. }
  7. public static void main(String[] args) {
  8. for (int i = 0; i < 10; i++) {
  9. new Thread(() -> {
  10. while (tickets.size() > 0) {
  11. System.out.println("销售了--" + tickets.remove(0));
  12. }
  13. }).start();
  14. }
  15. }
  16. }

输出结果如下,可以看到,编号0的车票被销售了两次。List不是同步容器,容器内的remove()等方法都无法做到原子性,因此会出现重复售票的问题,因此是不安全的:

程序二:使用Vector

以Vector代替List作为容器。区别是,Vector是同步容器,内部的方法都是同步的。但是下面的代码依然会存在问题。

虽然size()方法和remove()方法本身是原子性的,其他线程无法打断,但是在判断size和remove之间的部分依然会有线程交叉执行的可能,这样,虽然可以解决重复销售的问题,但是依然会导致:ArrayIndexOutOfBoundsException

  1. public class TicketSell_02 {
  2. static Vector<String> tickets = new Vector<>();
  3. static {
  4. for (int i = 0; i < 10000; i++)
  5. tickets.add("票编号: " + i);
  6. }
  7. public static void main(String[] args) {
  8. for (int i = 0; i < 10; i++) {
  9. new Thread(() -> {
  10. while (tickets.size() > 0) {
  11. try {
  12. TimeUnit.MILLISECONDS.sleep(5);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println("销售了--" + tickets.remove(0));
  17. }
  18. }).start();
  19. }
  20. }
  21. }

执行结果:

程序三:同步

使用synchronized进行线程同步,可以有效解决逻辑问题,但是很明显,这种方法的缺点就是效率低下。

  1. public class TicketSell_03 {
  2. static List<String> tickets = new LinkedList<>();
  3. static {
  4. for (int i = 0; i < 10000; i++)
  5. tickets.add("票编号: " + i);
  6. }
  7. public static void main(String[] args) {
  8. for (int i = 0; i < 10; i++) {
  9. new Thread(() -> {
  10. while (true) {
  11. synchronized (tickets) {
  12. if (tickets.size() == 0)
  13. break;
  14. System.out.println("销售了票--" + tickets.remove(0));
  15. }
  16. }
  17. }).start();
  18. }
  19. }
  20. }

程序四:并发容器Queue

ConcurrentLinkedQueue是一个并发队列但凡并发容器,其内部的方法都保证是原子性的。下面的代码中poll()表示从队列的头部获得一个数据,当返回值为null时,代表这个队列已经没有值了。因为队列本身不允许存null值,否则会报空指针异常,因此当返回值为null时,一定表示队列已空(size() == 0)。队列的底层是使用一个叫做CompareAndSet(CAS)的技术实现的,不是加锁的实现,因此在高并发的情况下依然可以拥有很高的效率。

  1. public class TicketSell_04 {
  2. static Queue<String> tickets = new ConcurrentLinkedQueue<>();
  3. static {
  4. for (int i = 0; i < 10000; i++)
  5. tickets.add("票编号: " + i);
  6. }
  7. public static void main(String[] args) {
  8. for (int i = 0; i < 10; i++) {
  9. new Thread(() -> {
  10. while (true) {
  11. String s = tickets.poll();
  12. if (s == null)
  13. break;
  14. else
  15. System.out.println("销售了--" + s);
  16. }
  17. }).start();
  18. }
  19. }
  20. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/149171
推荐阅读
相关标签
  

闽ICP备14008679号