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();
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM