JAVA集合-线程安全

一、其他的集合类

我们这里来介绍一点其他的集合类

1.1、Linkedhashmap

Linkedhashmap在原来的基础上维护了一个双向链表,用来维护,插入的顺序。

public class LinkedHashMapTest {

    public static void main(String[] args){
        Map<String,String> map = new LinkedHashMap<>(16);
        map.put("a","m");
        map.put("b","n");
        map.put("c","o");
        map.put("d","p");
        map.put("e","q");

        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, String> next = iterator.next();
            System.out.println(next.getKey());
            System.out.println(next.getValue());
        }
    }
}

我们看一下Linkedhashmap的构造器

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

这只是一个空参构造

原文对于accseeOrder的解释是这样的

accessOrder – the ordering mode - true for access-order, false for insertion-order
// accessOrder为true的话,则会把访问过的元素放在链表后面,放置顺序是访问的顺序 如果accessOrder为flase的话,则按插入顺序来遍历

可以用这个集合实现LRU算法

public class LRU<K,V> extends LinkedHashMap<K,V> {
    private int max_capacity;
    public LRU(){
        super(16,0.75F,true);
        max_capacity = 8;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > max_capacity;
    }
}

1.2、TreeMap

TreeMap底层实现是红黑树,所以天然支持排序。

我们看一下她的源码

public TreeMap() {
    comparator = null;
}

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
} 



final int compare(Object k1, Object k2) {
    return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
        : comparator.compare((K)k1, (K)k2);
}

他有两个构造器,我们实现一下空参构造

Map<Dog,String> map = new TreeMap<>();

for (int i = 0; i < 100; i++) {
    map.put(new Dog(),"a");
}

报错,没有定义比较器,我们定义一个比较器

Map<Integer,String> map = new TreeMap<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

for (int i = 0; i < 100; i++) {
        map.put(i,"a");
    }
// 我们发现已经自动排序了
System.out.println(map);

1.3、Collections

Collections是一个工具类,它给我们提供了一些常用的好用的操作集合的方法。

ArrayList<Integer> list = new ArrayList<>();
list.add(12);
list.add(4);
list.add(3);
list.add(5);
//将集合按照默认的规则排序,按照数字从小到大的顺序排序
Collections.sort(list);
System.out.println("list = " + list);
//将集合中的元素反转
Collections.reverse(list);
System.out.println("list = " + list);
//addAll方法可以往集合中添加元素,也可往集合中添加一个集合
Collections.addAll(list,9,20,56);
//打乱集合中的元素
Collections.shuffle(list);
System.out.println("list = " + list);
//Arrays.asList方法可以返回一个长度内容固定的List集合
List<String> list2 = Arrays.asList("tom", "kobe", "jordan", "tracy","westbook","yaoming","ace","stephen");
//按照字符串首字符的升序排列
Collections.sort(list2);

二、线程安全问题

2.1、并发修改异常

使用增强for循环中删除元素会抛异常,其实不仅仅是删除元素,增加元素也会导致异常

public void StrangerFor() {
    for (Integer array:arrayList) {
        arrayList.remove(array);
        System.out.println(array);
    }
}

迭代器是依赖于集合而存在的,在判断成功后,集合的中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。

  • 迭代器迭代元素,迭代器修改元素。
  • 集合遍历元素,集合修改元素(普通for)

2.2、线程安全

我们知道当多个线程同时操作共享资源时会有线程安全问题。我们试着去产生一下这个问题

ArrayListTest arrayListTest = new ArrayListTest();
    for (int i = 0; i < 1000; i++) {
        new Thread(()->{
            ArrayListTest.arrayList.add(1);
        }).start();
    }
System.out.println(arrayList.size());

最后添加的数据并不到1000个,总是差两三个

那怎么解决线程安全的问题啊。加锁,只能有一个线程去操作这个集合

三、加锁

3.1、HashTable

HashMap和HashTable区别

  1. HashMap允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
  2. HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue 和 containsKey。因为 contains 方法容易让人引起误解。
  3. HashTable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Map interface 的一个实现。
  4. HashTable 的方法是 Synchronized修饰 的,而 HashMap 不是,这也是是否能保证线程安全的重要保障。
  5. Hashtable 和 HashMap 采用的 hash/rehash 算法都不一样。
  6. 获取数组下标的算法不同,

3.2、Vector

ArrayList和Vector的区别

  1. Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多,有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;
  2. 两个都是采用线性连续空间存储元素,但是当空间不足的时候,两个类的扩容方式是不同的。
  3. Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

3.3、CopyOnWriteList

CopyOnWriteList的核心就是写入的时候加锁,保证线程安全,读取的时候不加锁。不是一股脑,给所有的方法加锁。

3.4、ConcurrentHashMap

ConcurrentHashMap和HashMap的代码基本一样,只不过在有些操作上使用了cas,有些地方加了锁。

JDK8以前是头插法,JDK8后是尾插法,那为什么要从头插法改成尾插法?

  1. 因为头插法会造成循环链表
  2. JDK7用头插是考虑到了一个所谓的热点数据的点(新插入的数据可能会更早用到),但这其实是个伪命题,因为JDK7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置(就是因为头插), 所以最后的结果 还是打乱了插入的顺序 ,所以总的来看支撑JDK7使用头插的这点原因也不足以支撑下去了 ,所以就干脆换成尾插 一举多得。

我们尝试开辟50个线程,每个线程向集合中put100000个元素,测试两个类所需要的时间。

@Test
public void hashtableTest() throws InterruptedException {
    final Map<Integer,Integer> map = new Hashtable<>(500000);
    final CountDownLatch countDownLatch = new CountDownLatch(50);
    System.out.println("----------------开始测试Hashtable------------------");
    long start = System.currentTimeMillis();
    for (int i = 0; i < 50; i++) {
        final int j = i;
        new Thread(()->{
            for (int k = 0; k < 100000; k++) {
                map.put(j*k,1);
            }
            countDownLatch.countDown();
        }).start();
    }
    countDownLatch.await();
    long end = System.currentTimeMillis();
    System.out.println("hashtable:(end-start) = " + (end - start));

    // ----------------开始测试ConcurrentHashMap------------------
    System.out.println("----------------开始测试ConcurrentHashMap------------------");
    final Map map2 = new ConcurrentHashMap<>(500000);
    final CountDownLatch countDownLatch2 = new CountDownLatch(50);
    start = System.currentTimeMillis();
    for (int i = 0; i < 50; i++) {
        final int j = i;
        new Thread(()->{
            for (int k = 0; k < 100000; k++) {
                map2.put(j*k,1);
            }
            countDownLatch2.countDown();
        }).start();
    }
    countDownLatch.await();
    end = System.currentTimeMillis();
    System.out.println("ConcurrentHashMap:(end-start) = " + (end - start));
}

最后我们发现ConcurrentHashMap真的比HashTable快了很多

四、guava提供了一些不可变集合

谷歌的程序员无聊的时候搞得一个工具包

你可能感兴趣的:(JAVA,java,安全,链表)