集合不安全:
-
常用的集合List:ArrayList线程不安全 多线程环境中会出现并发修改异常 下面的代码中使用 30个线程进行修改
-
具体问题:
public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 3; i++) { new Thread(() ->{ String replace = UUID.randomUUID().toString().substring(0, 5); list.add(replace); }).start(); } System.out.println(list); }
-
解决方式:
- 最粗暴的方式加锁: 使用synchronized 也就是使用 Vector 能解决上述的问题:但是出现了一个新的问题(数据的一致性得以实现 但是并发性降低 每次只能有一个线程执行写操作 非常的慢)
- Java api 中获取解决方式 将线程不安全的ArrayList转变成为线程安全的List 但是也不是能完全的实现线程安全 比如进行迭代操作:还是会出现上述的异常
List<String> list = Collections.synchronizedList(new ArrayList<>());
- 查看源码可知:大部分的操作都是使用synchronized 但是 iterator方法确实需要使用者手动的进行同步 非常的坑!!
- 查看源码可知:大部分的操作都是使用synchronized 但是 iterator方法确实需要使用者手动的进行同步 非常的坑!!
- 最粗暴的方式加锁: 使用synchronized 也就是使用 Vector 能解决上述的问题:但是出现了一个新的问题(数据的一致性得以实现 但是并发性降低 每次只能有一个线程执行写操作 非常的慢)
-
CopyOnWriteArrayList 从字面可以知道,CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段(读写分离)操作的代码如下:底层使用的是volatile 而不是比较重的synchronized 比较推荐的方式
/** The lock protecting all mutators */ transient final ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private volatile transient Object[] array;//保证了线程的可见性 public boolean add(E e) { final ReentrantLock lock = this.lock;//ReentrantLock 保证了线程的可见性和顺序性,即保证了多线程安全。 lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1);//在原先数组基础之上新建长度+1的数组,并将原先数组当中的内容拷贝到新数组当中。 newElements[len] = e;//设值 setArray(newElements);//对新数组进行赋值 return true; } finally { lock.unlock(); } }
- 其没有加任何同步关键字,根据以上写操作的代码可知,其每次写操作都会进行一次数组复制操作,然后对新复制的数组进行些操作,不可能存在在同时又读写操作在同一个数组上(不是同一个对象),而读操作并没有对数组修改,不会产生线程安全问题。Java中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。
- 其中setArray()操作仅仅是对array进行引用赋值。Java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间
- 在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操作,完成后再将引用移到新的列表上。旧列表如果仍在使用中(比如遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的情况(读和写分别发生在两个对象上),同时读操作也不必等待写操作的完成,免去了锁的使用加快了读取速度
-
Collections.synchronizedList & CopyOnWriteArrayList适用的场景:
- CopyOnWriteArrayList,发生修改时候做copy,新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存
- Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用,但是有需要同步列表的地方,读写操作都比较均匀的地方
-
-
常用集合Se:解决方式 和 List相同
- Set 解决方式:
- Set不安全解决方案一:collections().synchronizedSet(new Hashset());
- Set不安全解决方案二:copyOnWriteArraySet();
- Set不安全解决方案一:collections().synchronizedSet(new Hashset());
- Set 解决方式:
-
Map解决方式:
-
解决方式1:使用collections().synchronizedMap(new HashMap());
public static void collections(){ Map<String, String> map = Collections.synchronizedMap(new HashMap<>()); for (int i = 1; i <= 100; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } }
-
解决方式2 :ConcurrentHashMap
public static void concurrentHashMap(){ /** * 针对Map,没有 CopyOnWrite**类,与之对应的是ConcurrentHashMap */ Map<String, String> map = new ConcurrentHashMap<>(); for (int i = 1; i <= 100; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } }
-