从并发容器ConcurrentLinkedQueue看解决并发问题的设计思路

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/du771278794/article/details/78760757

本次问题模拟火车票售票情景,假设有一台服务器终端内部存储10000张火车票,终端下属十台客户端,十台客户端同时对外进行售票

1.代码模式-错误示例, 此模式下不能解决并发问题,因为每次判断与售票都要对容器进行操作

/**
 * 不使用任何辅助的时候高并发会出现线程不安全的漏洞
 * 线程容器List 不是同步的
 * 同时 判断跟递减之间没有原子性
 * Created by 阿杜 on 2017/12/9.
 */
public class TicketSell1 {
    private static List<String> tickets = new ArrayList();

    static{
        for(int i=0; i<10000; i++){
            tickets.add("这是第" + i + "张票");
        }
    }

    private static class BuySocket extends Thread{

        @Override
        public void run(){
            // 此处 线程不存在原子性 所以是不安全的
            while(tickets.size() > 0){
                System.out.println("销售了--->" + tickets.remove(0));
            }
        }
    }
    public static void main(String[] args) {
        for(int i=0; i<10; i++){
            new BuySocket().start();
        }
    }
}

2.代码模式-错误示例2  此模式使用了线程安全的容器Vector,解决了修改容器时可能出现的异常,但是判断跟修改容器之间仍然不存在原子性,所以问题依旧解决不了

/**
 * 使用了线程安全的容器Vector
 * 但是在条件判断跟减程序之间仍然是非原子性的所以还是会出现线程不安全的现象
 * Created by 阿杜 on 2017/12/9.
 */
public class TicketSell2 {
    private static Vector<String> tickets = new Vector();

    static {
        for(int i=0; i<10000; i++){
            tickets.add("这是第" + i + "张票");
        }
    }

    private static class BuyTicket extends Thread{

        @Override
        public void run(){
            while(tickets.size() > 0){

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("买到了--->" + tickets.remove(0));
            }
        }
    }

    public static void main(String[] args) {
        for(int i=0; i<10; i++){
            new BuyTicket().start();
        }
    }
}

3.代码示例-加锁 此模式下使用了synchronized关键字,引入了加锁机制,保证了判断跟修改容器之间的原子性,解决了问题,但是由于颗粒度较大所以效率被牺牲了

/**
 * 使用了线程安全的容器
 * 每次销售的时候锁定整个队列
 * 完成了原子性要求 但是效率不够高
 * Created by 阿杜 on 2017/12/9.
 */
public class TicketSell3 {
    private static List<String> tickets = new LinkedList();

    static {
        for(int i=0; i<10000; i++){
            tickets.add("这是第" + i + "张票");
        }
    }

    private static class BuyTicket extends Thread{

        @Override
        public void run(){
            while(true){

                synchronized(tickets){

                    if(tickets.size() <= 0) break;

/*                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/

                    System.out.println("买到了--->" + tickets.remove(0));
                }
            }
        }
    }

    public static void main(String[] args) {
        for(int i=0; i<10; i++){
            new BuyTicket().start();
        }
    }
}

4.代码模式-使用高并发容器ConcurrentLinkedQueue, 既提高了效率也解决了问题

/**
 * 使用了并发容器队列Queue
 * 线程安全 并且保证了原子性
 * Created by 阿杜 on 2017/12/9.
 */
public class TicketSell4 {
    private static Queue<String> tickets = new ConcurrentLinkedQueue();

    static {
        for(int i=0; i<10000; i++){
            tickets.add("这是第" + i + "张票");
        }
    }

    private static class BuyTicket extends Thread{

        @Override
        public void run(){
            while(true){
                String string = tickets.poll();
                if(string == null) break;
                System.out.println("买到了--->" + string);
            }
        }
    }

    public static void main(String[] args) {
        for(int i=0; i<10; i++){
            new BuyTicket().start();
        }
    }
}

通过上述四种模式可以看出四种模式最好,那就此问题分析一下问题的关键所在

当 多条线程共同访问一个容器的时候,如果要修改容器必然需要对容器进行判断,而只有对容器的判断与对容器的修改这两两种操作之间保证原子性的时候才可以确保线程安全,也就是说线程是否安全的关键在于判断跟修改同时都要操作容器。

根据问题分析,如果要解决问题,那就需要是把对容器的两次操作变为一次操作,也就是说先从容器获取元素,然后对元素进行判断与输出 此时只需要对容器进行一次操作,就算在元素的判断与输出之间不能保证原子性,那也不会出现不安全,因为不需要再次去操作容器,这样也就解决了并发时的线程不安全问题

问题解决的思路在于,原本判断、操作容器由容器之外的的程序控制改为了由容器内部进行控制,所以加锁的粒度也就更小了 效率会更加高

也就是说把原本客户需要去考虑的问题改为由服务在内部进行解决,进而提高体验度


展开阅读全文

没有更多推荐了,返回首页