java 并发编程学习笔记(五)之 不可变对象、同步容器、juc并发容器

                      不可变对象、同步容器、juc并发容器

(1)不可变对象:

  • 不可变对象需要满足的条件
  • 对象创建以后其状态就不能修改
  • 对象所有域都是final类型
  • 对象时正确创建(在对象创建期间,this引用没有溢出)

final 关键字:类、方法、变量

修饰类:不能被继承

修饰方法:锁定方法不能被继承类修改 ,效率

修饰变量:基本数据类型变量,引用类型变量

@Slf4j
public class ImmutableExample1 {

    private final static Integer a = 1;
    private final static String b = "2";
    private final static Map<Integer, Integer> map1 = Maps.newHashMap();

    static {
        map1.put(1, 2);
        map1.put(3, 4);
        map1.put(5, 6);
    }

    private static Map<Integer, Integer> map2 = Maps.newHashMap();
    private static List<Integer> list2 = new ArrayList<>();

    static {
        map2.put(1, 2);
        map2.put(3, 4);
        map2.put(5, 6);
        //通过Collections获取一个不可被修改的map
        map2 = Collections.unmodifiableMap(map2);

        list2.add(1);
        list2.add(2);
        //通过Collections获取一个不可被修改的list
        list2 = Collections.unmodifiableList(list2);
    }
    
    //通过guava 也可以获取不可变list,set ,map
    private static final ImmutableList<Integer> list3 = ImmutableList.of(11, 2, 3, 6);

    private static final List<Integer> list4 = new ArrayList<Integer>();

    private static final ImmutableMap<Integer, Integer> unMap = ImmutableMap.of(1, 2, 3, 4);

    private static final ImmutableMap<Integer, Integer> unMap1 = ImmutableMap.<Integer, Integer>builder().put(1, 6).build();

    static {
        list4.add(1);
        list4.add(2);
    }


    public static void main(String[] args) {
        //        a = 2 ;
        //        b ="3";
        //     map =Maps.newHashMap();
        map1.put(1, 3);
        log.info("{}", map1.get(1));


        map2.put(1, 3);
        list2.add(4);
        log.info("{}", map2.get(1));


        list3.add(56);
        ImmutableSet list2 = ImmutableSet.copyOf(list4);
        list2.add(7);
        unMap.put(1, 6);
        unMap1.put(1, 8);
    }
}

 (2)   线程 封闭

《java 并发编程学习笔记(五)之 不可变对象、同步容器、juc并发容器》

public class RequestHolder {

    private final static ThreadLocal<Long> requestHolder = new ThreadLocal<Long>();

    public static void add(Long id) {
        requestHolder.set(id);
    }

    public static Long getId() {
        return requestHolder.get();
    }

    public static void remove() {
        requestHolder.remove();
    }
}

(3)常见的线程不安全的类

public class StringExample1 {

    //请求次数
    private static int clientTotal = 5000;

    //允许同时运行的线程数
    private static int threadTotal = 200;

    /**
     * stringBuilder 线程不安全
     * stringBuffer 线程安全
     */
    //public static  StringBuilder stringBuilder=new StringBuilder();
    public static StringBuffer stringBuffer = new StringBuffer();

    /**
     * simpleDateFormat 不是线程安全的
     * joda-time 的dateTimeFormatter 是线程安全的
     */
    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");

    public static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy/MM/dd");

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    append();
                    semaphore.release();
                } catch (InterruptedException e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });

        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        log.info("count:{}", stringBuffer.length());
    }

    private static void append() {
        DateTime dateTime = DateTime.parse("2018/06/07", dateTimeFormatter);
        log.info(dateTime.toDate().toString());
    }
}

 (4)同步容器

《java 并发编程学习笔记(五)之 不可变对象、同步容器、juc并发容器》

 

@Slf4j
public class ContainExample {

    //请求次数
    private static int clientTotal = 5000;

    //允许同时运行的线程数
    private static int threadTotal = 200;

    private static List<Integer> list = new ArrayList<Integer>();
    private static Vector<Integer> vector =new Vector<>();
    static {
        vector.add(1);
        vector.add(2);
        vector.add(3);
    }
    private  static  List<Integer> safeList  = Collections.synchronizedList(new ArrayList<Integer>());

    private static Set<Integer> set =new HashSet<Integer>();
    private  static  Set<Integer> safeSet  = Collections.synchronizedSet(new HashSet<Integer>());

    private static  Map<Integer,Integer> map = new HashMap<Integer, Integer>();
    private static Map<Integer,Integer> safeMap = new Hashtable<>();
    private  static  Map<Integer,Integer> safeMap1  = Collections.synchronizedMap(new HashMap<Integer,Integer>());

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
             final int index = i;
             executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    putValue(index);
                    semaphore.release();
                } catch (InterruptedException e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });

        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        log.info("count:{}", safeMap.size());

      //  testVector();
        testVector1();
    }

    private static void putValue(int i) {
        safeMap.put(i,i);
    }



    public  static  void  testVector(){

        //ExecutorService executorService = Executors.newCachedThreadPool();
        //在这种情况下 线程的 vector 也会变得线程不安全
        while(true) {
            new Thread(() -> {
                for (int i = 0, l = vector.size(); i < l; i++) {
                    vector.remove(i);
                }
            }).start();

            new Thread(() -> {
                for (int i = 0, l = vector.size(); i < l; i++) {
                    vector.get(i);
                }
            }).start();
        }
    }

    public  static  void  testVector1(){
        try {
            for (Integer i : vector) {    //不推荐
               if(i == 3){
                   vector.remove(i);
               }
            }
        }catch (Exception e){
            log.error("foreach循环删除时报错",e);
        }

        try {
            Iterator<Integer> iterator = vector.iterator();
            while(iterator.hasNext()){       //推荐
                  if(iterator.next() == 3){
                      iterator.remove();
                  }
            }
        }catch (Exception e){
            log.error("iterator循环删除时报错",e);
        }


        try {
            for (int i =0; i< vector.size();i++) {  //推荐
                if(i == 3){
                    vector.remove(i);
                }
            }
        }catch (Exception e){
            log.error("for循环删除时报错",e);
        }
    }
}

(5)并发容器  

《java 并发编程学习笔记(五)之 不可变对象、同步容器、juc并发容器》

  •  CopyOnWriteArrayList

CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。与ArrayList不同处就在于是否会拷贝数组和加锁.

CopyOnWriteArrayList顾名思义就是写时复制的ArrayList,其意思就是在修改容器的元素时,并不是直接在原数组上修改,而是先拷贝了一份数组,然后在拷贝的数组上进行修改,修改完后将其引用赋值给原数组的引用。这样体现了读写分离,这样无论在任何时候我们都可以对容器进行读取。

所谓动态数组操作机制:即通过volatile修饰的Object类型数组来进行数组的CRUD操作。在进行add,set,remove等可变操作的时候,都会先新建一个数组把更新的值赋给该数组,然后再传递给上面的array数组来保持该次操作的可见性。这也是CopyOnWriteArrayList命名的由来。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,即在进行读操作时的效率要远远高于写或是修改操作,这种方法可能比其他替代方法更 有效。

  • CopyOnWriteArraySet

它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过“动态数组(CopyOnWriteArrayList)”实现的,并不是散列表。
和CopyOnWriteArrayList类似,CopyOnWriteArraySet具有以下特性:
1. 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
2. 它是线程安全的。
3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
4. 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

  •  ConcurrentSkipListSet

ConcurrentSkipListSet是线程安全的有序的集合,适用于高并发的场景。
ConcurrentSkipListSet和TreeSet,它们虽然都是有序的集合。但是,第一,它们的线程安全机制不同,TreeSet是非线程安全的,而ConcurrentSkipListSet是线程安全的。第二,ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,而TreeSet是通过TreeMap实现的。

  • ConcurrentHashMap

ConcurrentHashMap是线程安全且高效的HashMap。正常业务场景中,我们会经常会用到HashMap,而在多线程环境下,java.util.Hashmap进行put操作时会导致死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会死循环获取Entry。HashMap put时,发生死循环的原因是因为rehash时导致

而线程安全的HashTable 使用synchronized来保证线程安全,在线程锁竞争激烈的情况下 HashTable的效率非常低下。在Hashtable里,同一把锁连get都会使用synchronized来保证线程安全,Hashtable会竞争同一把锁,所以效率低下。若是能够变成多把锁,就能有效提升并发的效率。ConcurrentHashMap采用了锁分段技术,并且设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响

  • ConcurrentSkipListMap

ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上能够在O(log(n))时间内完成查找、插入、删除操作。
         SkipList是一种红黑树的替代方案,由于SkipList与红黑树相比无论从理论和实现都简单许多,所以得到了很好的推广。SkipList是基于一种统计学原理实现的,有可能出现最坏情况,即查找和更新操作都是O(n)时间复杂度,但从统计学角度分析这种概率极小。
使用SkipList类型的数据结构更容易控制多线程对集合访问的处理,因为链表的局部处理性比较好,当多个线程对SkipList进行更新操作(指插入和删除)时,SkipList具有较好的局部性,每个单独的操作,对整体数据结构影响较小。而如果使用红黑树,很可能一个更新操作,将会波及整个树的结构,其局部性较差。因此使用SkipList更适合实现多个线程的并发处理。
         在非多线程的情况下,应当尽量使用TreeMap。此外对于并发性相对较低的并行程序可以使用Collections.synchronizedSortedMap将TreeMap进行包装,也可以提供较好的效率。对于高并发程序,应当使用ConcurrentSkipListMap,能够提供更高的并发度。
      所以在多线程程序中,如果需要对Map的键值进行排序时,请尽量使用ConcurrentSkipListMap,可能得到更好的并发度。
      注意,调用ConcurrentSkipListMap的size时,由于多个线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素个数,这个操作是个O(log(n))的操作。

在JDK1.8中,ConcurrentHashMap的性能和存储空间要优于ConcurrentSkipListMap,但是ConcurrentSkipListMap有一个功能: 它会按照键的自然顺序进行排序。

 总结:

线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改

共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是

任何线程都不能修改它

线程安全对象: 一个线程安全的对象或者容器,在内部通过同步机制保证线程安全,所以其他线程

无需额外的同步就可以通过公共接口随意访问它

被守护对象:被守护对象只能通过获取特定的锁来访问

 

    原文作者:JUC
    原文地址: https://blog.csdn.net/qq_31905135/article/details/84248800
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞