zoukankan      html  css  js  c++  java
  • Java高并发编程(三)

     目录:

      1.线程安全单例模式的几种实现方式

      2.同步容器

      3.并发容器

      一、线程安全单例模式的几种实现方式

      1.饿汉式(不使用同步锁,典型的用空间换时间)

    public class Singleton1 {  
     
      private static Singleton1 mySingleton = new Singleton1();  
    
      private Singleton1(){   
          System.out.println("single");  
      }  
         
      public static Singleton1 getSingle(){  
          return mySingleton;  
      }
      
      public static void main(String[] args) {
            Thread[] ths = new Thread[200];
            for(int i=0; i<ths.length; i++) {
                ths[i] = new Thread(()->{
                    Singleton1.getSingle();
                });
            }
            
            Arrays.asList(ths).forEach(o->o.start());
        }
    } 

    运行结果:

      2.懒汉式(使用同步锁,延时加载,典型的时间换空间)

    public class Singleton2 {  
        
        private static Singleton2 mySingleton;  
        
        private Singleton2 (){  
             System.out.println("single"); 
        }   
          
        public static synchronized Singleton2 getSingle(){    //对获取实例的方法进行同步
            if (mySingleton == null)     
                mySingleton = new Singleton2(); 
            return mySingleton;
        }
        
        public static void main(String[] args) {
            Thread[] ths = new Thread[200];
            for(int i=0; i<ths.length; i++) {
                ths[i] = new Thread(()->{
                    Singleton2.getSingle();
                });
            }
                
            Arrays.asList(ths).forEach(o->o.start());
        }
    }  

    运行结果:

      3.双重同步锁(缩小粒度,双重检查

    public class Singleton3 {  
         
        private volatile static Singleton3 mySingleton;  
         
        private Singleton3 (){
            System.out.println("single");
        }   
         
        public static Singleton3 getSingle(){    //对获取实例的方法进行同步
           if (mySingleton == null){
               synchronized(Singleton3.class){
                   if (mySingleton == null)
                       mySingleton = new Singleton3(); 
               }
           }
           return mySingleton;
        }
        
        public static void main(String[] args) {
            Thread[] ths = new Thread[200];
            for(int i=0; i<ths.length; i++) {
                ths[i] = new Thread(()->{
                    Singleton3.getSingle();
                });
            }
                
            Arrays.asList(ths).forEach(o->o.start());
        }
    }

    运行结果:

      为mySingleton加上volatile关键字,以确保能先行发生关系(happens-before relationship),使所有的写操作都发生于读操作之前,避免出现在另一个线程中看到一个初始化一半的mySingleton的情况,同时双重同步锁的效率也很高

      4.使用内部类的单例模式(既不用加锁,也能实现懒加载)

    public class Singleton4 {
        
        private Singleton4() {
            System.out.println("single");
        }
        
        private static class Inner {
            private static Singleton4 mySingleton = new Singleton4();
        }
        
        public static Singleton4 getSingle() {
            return Inner.mySingleton;
        }
        
        public static void main(String[] args) {
            Thread[] ths = new Thread[200];
            for(int i=0; i<ths.length; i++) {
                ths[i] = new Thread(()->{
                    Singleton4.getSingle();
                });
            }
            
            Arrays.asList(ths).forEach(o->o.start());
        }
        
    }

    运行结果:

      二、同步容器

      1.vector、stack、hashtable等,保证了方法的原子性

      2.小例子程序演示同步容器

    /**
     * 有N张火车票,每张票都有一个编号
     * 同时有10个窗口对外售票
     * 请写一个模拟程序
     * 
     * 分析下面的程序可能会产生哪些问题?
     * 重复销售?超量销售?
     * 
     */
    package yxxy.c_024;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class TicketSeller1 {
        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();
            }
        }
    }

    运行结果:

    出现了越界情况,说明发生了超量销售与重复销售的情况

    下面使用同步容器,以vector为例再做一遍售票操作

    /**
     * 有N张火车票,每张票都有一个编号
     * 同时有10个窗口对外售票
     * 请写一个模拟程序
     * 
     * 分析下面的程序可能会产生哪些问题?
     *  
     * 使用Vector或者Collections.synchronizedXXX
     * 分析一下,这样能解决问题吗?
     * 
     */
    package yxxy.c_024;
    
    import java.util.Vector;
    import java.util.concurrent.TimeUnit;
    
    public class TicketSeller2 {
        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 {
                            TimeUnit.MILLISECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }*/
                        
                        
                        System.out.println("销售了--" + tickets.remove(0));
                    }
                }).start();
            }
        }
    }

    运行结果:

    ...

    从运行结果来看,同步容器似乎已经达到了我们想要的目的,但其中还有一些问题

    当我们使用了同步容器时,虽然容器的方法是具有原子性的,但当判断与操作分离时,中间的部分还是可能会被其他线程所打断,下面我们模拟一下判断与remove之间有其他的逻辑

    /**
     * 有N张火车票,每张票都有一个编号
     * 同时有10个窗口对外售票
     * 请写一个模拟程序
     * 
     * 分析下面的程序可能会产生哪些问题?
     *  
     * 使用Vector或者Collections.synchronizedXXX
     * 分析一下,这样能解决问题吗?
     * 
     */
    package yxxy.c_024;
    
    import java.util.Vector;
    import java.util.concurrent.TimeUnit;
    
    public class TicketSeller2 {
        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 {
                            TimeUnit.MILLISECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                        
                        System.out.println("销售了--" + tickets.remove(0));
                    }
                }).start();
            }
        }
    }

    运行结果:

    我们看到程序出现了错误,所以虽然同步容器保证了方法的原子性,但可能会发生判断与操作分离,整体操作不具备原子性

      三、并发容器

      1.并发容器支持多线程

      ConcurrentSkipListMap跳表结构,他是排好序的容器执行插入操作较快

      ConcurrentHashMap将容器分成了16段,每次执行插入操作的时候只执行其中的一段,而HashTable为锁定一整段

      ConcurrentLinkedQueue无界队列,内存耗不完的情况下可以一直加(offer,add),拿(poll拿出来删掉,peek拿出来不删)

      BolckingQueue阻塞式队列,加(put如果满了就会等待),拿(take如果空了就会等待)

        几个特殊的阻塞式队列:

        1)TransferQueue转移队列,相较于其他队列,多了一个transfer方法生产者将任务直接发给消费者,不进入结束队列,在transfer找不到消费者情形下阻塞,实时处理用的较多,队列还有一定容量

        2)DelayQueue计时任务队列

        3)synchronusQueue同步队列,特殊的transferQueue,没有容量的队列,不能调用add方法,只能调用put方法阻塞等待消费者消费,生产者生产的任务直接给消费者消费

     还是之前的售票程序,现在改用并发容器以ConcurrentLinkedQueue来重新实验一遍

    /**
     * 有N张火车票,每张票都有一个编号
     * 同时有10个窗口对外售票
     * 请写一个模拟程序
     * 
     * 使用ConcurrentQueue提高并发性
     */
    package yxxy.c_024;
    
    import java.util.Queue;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.TimeUnit;
    
    public class TicketSeller4 {
        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();
            }
        }
    }

    运行结果:

    当我们改用并发容器ConcurrentLinkedQueue时,我们调用poll方法售票直至票数为0时,字符串s会变为null,所以在接下来的if判断后会跳出

    当我们在此例中使用了并发容器时,就具有高的效率同时也不会出现问题

      四、其他常用同步容器

      1.Collocations.synchronizedXXX()将未加锁的容器加锁,在并发度不高的情况下可以使用此方法,在并发度较高且需排序的情况下使用concurrentSkipListMap

      2.CopyOnWriteList写时复制链接表,在读取时不加锁,因此读取的时候效率高,但写的效率特别低,应用场景:在需要大量读取少量写入时使用

      五、总结

      1.对于map/set的使用

        无线程安全要求:hashmap、treemap、linkedhashmap

        并发度低:   hashtable、Collections.synchronizedXXX()

        并发度高:   concurrentHashMap、concurrentSkipListMap

      2.list

        无线程安全要求:ArrayList、LinkedList

        并发度低:   Collections.synchronizedXXX()

        并发度高:   Queue

                  concurrentLinkedQueue

                  BlockingQueue

                    LinkedBQ

                    ArrayBQ

                    TransferQueue

                    synchronusQueue

                  DelayQueue

  • 相关阅读:
    实验一 命令解释程序
    复利计算器3.0更新版
    复利计算器3.0总结
    复利计算器2.0
    0414复利计算5.1-美观、输入更新
    0408结对汉堡-结对2.0
    0406复利计算程序5.0-结对编程
    《构建之法》第四章读后感
    复利计算4.0-单元测试
    操作系统实验一、 命令解释程序的编写
  • 原文地址:https://www.cnblogs.com/ghoster/p/7484831.html
Copyright © 2011-2022 走看看