并发编程之售票案例

* 场景

 * 有N张火车票,每张票都有一个编号

 * 同时有10个窗口对外售票

 * 请写一个模拟程序

 

 * 分析下面的程序可能会产生哪些问题?

 * 重复销售?超量销售?

Solution1:使用线程不安全的集合而且不上锁

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 场景:有N张火车票,每张票有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 * 
 * 分析下面的程序可能产生哪些问题?
 * 重复销售?超量销售?
 * @author 16114
 *
 */
//Solution1:使用线程不安全的集合而且不上锁
class Solution {
    static List<String> tickets = new ArrayList<>();
    static {
        for (int i = 0; i < 1000; i++) 
            tickets.add("票编号:" + i);
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                while(tickets.size() > 0) {   //①
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("销售了--" 
                            + tickets.remove(0));  //②
                }
            }).start(); 
        }
    }
}

报错:

销售了--票编号:991
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: -1

 

①程序逻辑的线程不安全:有可能多个线程涌入while循环是造成并发问题的根本,这个原因会导致size为1的时候涌入很多线程接而执行多次删除操作下标越界。【超卖】

②集合的线程不安全:remove方法本来就不是线程安全的。

可以看出如果两个线程同时执行remove方法的话,由于index一样所以他们的remove的返回值就会得到同一个oldValue。也就是重复卖出。【重卖】

 

 

Solution2:使用集合Vector但是代码逻辑不加锁

 

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
 * 场景:有N张火车票,每张票有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 * 
 * 分析下面的程序可能产生哪些问题?
 * 重复销售?超量销售?
 * @author 16114
 *
 */
//Solution2:使用集合Vector但是代码逻辑不加锁
class Solution {
    static Vector<String> tickets = new Vector<>();
    static {
        for (int i = 0; i < 1000; i++) 
            tickets.add("票编号:" + i);
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                while(tickets.size() > 0) { //
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("销售了--" 
                            + tickets.remove(0));//
                }
            }).start(); 
        }
    }
}

报错:

销售了--票编号:999
Exception in thread "Thread-9" java.lang.ArrayIndexOutOfBoundsException: 
Array index out of range: 0

Vector是线程安全的,所以它只会出现程序逻辑不安全带来的并发问题(超卖)。也就是会有可能多个线程涌入while循环中,这是造成并发问题的根本。会导致size为1时涌入很多线程接而执行多次删除下标越界。但是绝不会卖出同一张票(卖重)。

我们把remove的代码放大:

 

 

 

这是一个同步的方法,每一个线程过来如果得不到锁得话都会陷入等待。虽然都是remove(0)但是当下一个线程来到的时候0位置已经是一个全新的元素。

 

Solution3:给代码逻辑上锁使用线程不安全的集合

不多说了无论如何都可以防止线程安全问题,因为在Solution1中已经提到过了代码的并发问题是一切问题的原因。直接上代码:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 场景:有N张火车票,每张票有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 * 
 * 分析下面的程序可能产生哪些问题?
 * 重复销售?超量销售?
 * @author 16114
 *
 */
//Solution3:给代码逻辑上锁使用线程不安全的集合
class Solution {
    static List<String> tickets = new ArrayList<>();
    static {
        for (int i = 0; i < 1000; i++) 
            tickets.add("票编号:" + i);
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                while(tickets.size() > 0) { //
                    synchronized(tickets) {
                        if(tickets.size() <= 0) break;
                        try {
                            TimeUnit.MILLISECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("销售了--" 
                            + tickets.remove(0));//
                }
            }).start(); 
        }
    }
}

Solution4:使用并发集合不加锁

首先说并发的集合是线程安全的,而且效率较高因为使用了局部锁。这样的话只在取值的时候加了锁,而且如果是以下标来取值的话还可以同时取走多个地方的值这样的话效率大大提高。而且这里使用一种先取值后再判断的逻辑巧妙的避免了下标越界的错误,而前面的案例中都是先判断再取值,这样就造成了线程不安全。

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

/**
 * 场景:有N张火车票,每张票有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 * 
 * 分析下面的程序可能产生哪些问题?
 * 重复销售?超量销售?
 * @author 16114
 *
 */
//Solution3:给代码逻辑上锁使用线程不安全的集合
class Solution {
    static Queue<String> tickets = new ConcurrentLinkedQueue<>();
    static {
        for (int i = 0; i < 1000; 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;
                    else System.out.println("销售了--" + s);
                }
            }).start(); 
        }
    }
}

高并发集合简介

ConcurrectHashMap:使用了局部锁,也就是细粒度的锁来提高并发效果。

ConcurrectSkipListMap:使用了局部锁,但是结果也排序的map集合。对比TreeMap一个元素排序的map集合。

CopyOnWriteArrayList:读取时不加锁,但是写的时候回拷贝原有的数据然后对拷贝的数据进行操作最后将指针指向修改过的集合。这个集合适用于读操作远远大于写操作的情况。

BlockingQueue:阻塞队列,当队列中没有元素的时候就会对取元素产生阻塞,当队列中满元素的时候就会对添加元素产生阻塞。而且不允许添加null的值而且在取值与添加值的情况下都会加锁,换句话说它是一个线程安全的集合。以下为部分源码:

DelayQueue:执行定时任务,他的内部会装有很多的task接受的task都实现了Delay接口,因此task内部也就维护了一个conpareTo的方法,如果按照时间排序的话那么就能够实现任务

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * DelayQueue:执行定时任务
 * 他的内部会装有很多的task
 * 接受的task都实现了Delay接口,
 * 因此task内部也就维护了一个compareTo的方法,
 * 如果按照时间排序的话那么就能够实现任务的定时执行。
 */
class Solution {
    static BlockingQueue<MyTask> tasks = new DelayQueue();
    static Random r = new Random();
    static class MyTask implements Delayed {
        long runningTime;
        public MyTask(long r) {
            this.runningTime = r;
        }
        @Override
        public int compareTo(Delayed o) {
            if(this.getDelay(TimeUnit.MILLISECONDS) 
                    < o.getDelay(TimeUnit.MILLISECONDS))
                return -1;
            else if(this.getDelay(TimeUnit.MILLISECONDS) 
                    > o.getDelay(TimeUnit.MILLISECONDS))
                return 1;
            else 
                return 0;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(runningTime - System.currentTimeMillis(),
                    TimeUnit.MILLISECONDS);
        }
        @Override
        public String toString() {
            return "" + runningTime;
        }
        
    }
    public static void main(String[] args) throws InterruptedException {
        long now = System.currentTimeMillis();
        MyTask t1 = new MyTask(now + 1000);
        MyTask t2 = new MyTask(now + 2000);
        MyTask t3 = new MyTask(now + 1500);
        MyTask t4 = new MyTask(now + 2500);
        MyTask t5 = new MyTask(now + 500);
        tasks.put(t1);
        tasks.put(t2);
        tasks.put(t3);
        tasks.put(t4);
        tasks.put(t5);
        System.out.println(tasks);
        for (int i = 0; i < 5; i++) {
            System.out.println(tasks.take());
        }
    }
}

TransferQueue:当有消费者的话那么就直接将生产出来的元素交给消费者,但是如果没有消费者的话就会将生产的元素放到队列中。当队列中的元素消耗完的时候消费者就会阻塞。

 

posted @ 2019-03-03 11:09  Roni_i  阅读(452)  评论(0编辑  收藏  举报