zoukankan      html  css  js  c++  java
  • Java集合多线程安全

    线程安全与不安全集合

    线程不安全集合:

    • ArrayList
    • LinkedList
    • HashMap
    • HashSet
    • TreeMap
    • TreeSet
    • StringBulider

    线程安全集合:

    • Vector
    • HashTable
    • Properties

    集合线程安全与解决方案

    ArrayList线程安全问题

    package com.raicho.mianshi.mycollection;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    
    /**
     * @author: Raicho
     * @Description:
     * @program: mianshi
     * @create: 2020-07-17 15:32
     **/
    public class ArrayListConcurrentDemo {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 30; ++i) {
                new Thread(() -> {
                    list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                    System.out.println(list);
                }).start();
            }
        }
    }

    运行报错:


    ArrayList是线程不安全的,add()方法并没有加锁(synchronized),多线程环境下会抛出ConcurrentModificationException

    解决方案:

    • 使用Vector类(使用了synchronized),效率极低

    •  使用Collections.synchronizedList(new ArrayList<>()):内部直接将接受的List对象传递给静态内部类SynchronizedList对象,然后Collections.synchronizedList(new ArrayList<>())返回的List对象的调用方法都是直接调用输入List对象的方法,但是加了synchronized,类似装饰器模式,也是对输入List的一种增强:
    package com.raicho.mianshi.mycollection;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.UUID;
    
    /**
     * @author: Raicho
     * @Description:
     * @program: mianshi
     * @create: 2020-07-17 15:32
     **/
    public class ArrayListConcurrentDemo {
    
        public static void main(String[] args) {
            List<String> list = Collections.synchronizedList(new ArrayList<>());
            for (int i = 0; i < 50; ++i) {
                new Thread(() -> {
                    list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    

    源码:

    static <T> List<T> synchronizedList(List<T> list, Object mutex) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list, mutex) :
                new SynchronizedList<>(list, mutex));
    }
    
    static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        private static final long serialVersionUID = -7754090372962971524L;
    
        final List<E> list;
    
        SynchronizedList(List<E> list) {
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }
    
        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return list.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return list.hashCode();}
        }
    
        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
        public E remove(int index) {
            synchronized (mutex) {return list.remove(index);}
        }
    
        public int indexOf(Object o) {
            synchronized (mutex) {return list.indexOf(o);}
        }
        public int lastIndexOf(Object o) {
            synchronized (mutex) {return list.lastIndexOf(o);}
        }
    
        public boolean addAll(int index, Collection<? extends E> c) {
            synchronized (mutex) {return list.addAll(index, c);}
        }
    
        public ListIterator<E> listIterator() {
            return list.listIterator(); // Must be manually synched by user
        }
    
        public ListIterator<E> listIterator(int index) {
            return list.listIterator(index); // Must be manually synched by user
        }
    
        public List<E> subList(int fromIndex, int toIndex) {
            synchronized (mutex) {
                return new SynchronizedList<>(list.subList(fromIndex, toIndex),
                                            mutex);
            }
        }
    
        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            synchronized (mutex) {list.replaceAll(operator);}
        }
        @Override
        public void sort(Comparator<? super E> c) {
            synchronized (mutex) {list.sort(c);}
        }
    
        private Object readResolve() {
            return (list instanceof RandomAccess
                    ? new SynchronizedRandomAccessList<>(list)
                    : this);
        }
    }
    • CopyOnWriteArrayList:写时复制是一种读写分离的思想,在并发读的时候不需要加锁,因为它能够保证并发读的情况下不会添加任何元素。而在并发写的情况下,需要先加锁,但是并不直接对当前容器进行写操作。而是先将当前容器进行复制获取一个新的容器,进行完并发写操作之后,当之前指向原容器的引用更改指向当前新容器。也就是说,并发读和并发写是针对不同集合,因此不会产生并发异常
    package com.raicho.mianshi.mycollection;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.UUID;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    /**
     * @author: Raicho
     * @Description:
     * @program: mianshi
     * @create: 2020-07-17 15:32
     **/
    public class ArrayListConcurrentDemo {
    
        public static void main(String[] args) {
            List<String> list = new CopyOnWriteArrayList<>();
            for (int i = 0; i < 30; ++i) {
                new Thread(() -> {
                    list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }

    源码:

    // CopyOnWriteArrayList.java
    public boolean add(E e) {
        // 写操作加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 原有容器复制一份
            Object[] elements = getArray();
            int len = elements.length;
            // 创建一个容器,将原来的数据复制到新容器中,并且还有一个位置空余
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 将新元素添加到空余位置
            newElements[len] = e;
            // 将原来指向旧容器的引用指向新容器
            setArray(newElements);
            return true;
        } finally {
            // 写操作完成,解锁
            lock.unlock();
        }
    }
    
    public E set(int index, E element) {
        // 更新操作类似
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);
    
            if (oldValue != element) {
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    
    // 读操作不加锁
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    

    在添加元素e完后,再调用setArray(newElements);函数重新赋值,之前指向原容器的引用更改指向当前新容器

    HashSet线程安全问题

    HashSet底层就是一个HashMap,默认的HashSet是一个初始大小为16,负载因子为0.75的HashMap:

     

    HashSet的多线程安全问题实际上就是HashMap的多线程安全问题:
    package com.raicho.mianshi.mycollection;
    
    import java.util.HashSet;
    import java.util.Set;
    import java.util.UUID;
    /**
     * @author: Raicho
     * @Description:
     * @program: mianshi
     * @create: 2020-07-17 17:03
     *
     * HashSet多线程不安全问题
     * HashSet底层就是HashMap,因此这个案例也是HashMap多线程不安全问题的演示
     */
    public class HashSetThreadUnsafe {
        public static void main(String[] args) {
            Set<String> sets = new HashSet<>();
            for (int i = 0; i < 100; ++i) {
                new Thread(() -> {
                    sets.add(UUID.randomUUID().toString().substring(0, 4));
                    System.out.println(sets);
                },String.valueOf(i)).start();
            }
        }
    }
    

     解决方案:

    • Collections集合类的static方法SynchronizedSet
    • CopyOnWriteArraySet:也是写时复制思想,但是内部还是使用CopyOnWriteArrayList实现:
    public class CopyOnWriteArraySet<E> extends AbstractSet<E>
            implements java.io.Serializable {
        private static final long serialVersionUID = 5457747651344034263L;
    
        private final CopyOnWriteArrayList<E> al;
    
        /**
         * Creates an empty set.
         */
        public CopyOnWriteArraySet() {
            // 构造器内部实例化了一个CopyOnWriteArrayList
            al = new CopyOnWriteArrayList<E>();
        }
        // ...
    }
    

    HashMap多线程安全的解决方案

    相比于HashSet,HashMap除了可以使用Collections集合类的synchronizedMap方法外,还可以使用juc包下ConcurrentHashMap类。

    package com.raicho.mianshi.mycollection;
    
    import java.util.Map;
    import java.util.Set;
    import java.util.UUID;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    /**
     * @author: Raicho
     * @Description:
     * @program: mianshi
     * @create: 2020-07-17 17:03
     */
    public class HashMapThreadUnsafe {
        public static void main(String[] args) {
            Map<String,String> map = new ConcurrentHashMap<>();
            for (int i = 0; i < 100; ++i) {
                new Thread(() -> {
                    map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 4));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
        }
    }
  • 相关阅读:
    ssh -vT git@github.com get “ No such file or directory” 错误
    提高Bash使用效率的方法
    mybatis的update使用选择
    Ping 的TTL理解
    为什么要使用oath协议?
    Rest Client插件简单介绍
    idea中查看java类继承图
    CSS单行文本溢出显示省略号
    js里父页面与子页面的相互调用
    css font的简写规则
  • 原文地址:https://www.cnblogs.com/lzhdonald/p/13332727.html
Copyright © 2011-2022 走看看