java 集合线程不安全

java的集合框架

在这里插入图片描述

java 集合

在java Collection 接口上面 我们可以看到和它相关的一系列子接口以及实现类(map 在这里的原因, 可能因为它也是一个容器趴) Collection 接口只是为了一系列的容器操作提供了抽象的定义,这些定义在不同的子类中具体着不同的实现, 除此之外还定义了一些抽象的子类 AbstractCollection 在这个抽象类中 提供了一部分常用方法的通用实现, 可交由子类来重写来适应自己。

java 集合主要的继承关系

java 集合主要有两个接口, 分别是 Collection、 Map

  • Collection
    • Set
    • List
    • Queue
  • Map
 * @see     Set
 * @see     List
 * @see     Map
 * @see     SortedSet
 * @see     SortedMap
 * @see     HashSet
 * @see     TreeSet
 * @see     ArrayList
 * @see     LinkedList
 * @see     Vector
 * @see     Collections
 * @see     Arrays
 * @see      
 * @since 1.2
 */

public interface Collection<E> extends Iterable<E> {}

Set

Set集合中的元素是无序, 能够对元素进行去重! 主要实现类如下

  • HashSet
    • 底层使用HashMap
  • TreeSet
    • 底层使用二叉树
  • LinkedHashSet
    • 底层使用LinkedHashMap
  • CopyOnWriteArraySet
    • CopyOnWriteArrayList
      • 添加元素的时候采用写入时复制的原则

常用操作

1 添加元素、遍历集合

    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < 10; i++) {
            set.add(i);
        }

        for (Integer integer : set) {
            System.out.println(integer);
        }
		// 遍历的时候 就可以看到set中 只有一份 0 - 9 去重!
         for (Iterator<Integer> iterator = set.iterator(); iterator.hasNext(); ) {
             System.out.println(iterator.next());
         }
    }

2 底层使用的是HashMap

   public HashSet() {
        map = new HashMap<>();
    }

List

List 中的元素是有序的,并且可重复。

  • ArrayList
    • 数组做为自己的底层实现
  • LinkedList
    • 链表做为自己的底层实现
    • 提供了操作头部、尾部元素的方法
  • Vector
    • 数组做为自己的底层实现
    • 并且所有的方法, 都是用synocrized修饰,访问的效率没有 ArrayList 高
  • CopyOnWriteArrayList
    • 数组实现, 使用写入时复制的原则
    • 线程安全

ArrayList 的常见 API

    private static void test(List<Integer> list) {
        for (int i = 0; i <= 10; i++) {
            list.add(i);
        }
        for (int i = 0; i <= 10; i++) {
            list.add(i);
        }
        list.remove(5);
        System.out.println(list.indexOf(3));
        System.out.println(list.lastIndexOf(4));
        Object[] array = list.toArray();
        list.set(1, 2);
        System.out.println(list.remove(2));
        System.out.println(list.get(0));
        System.out.println(list.size());
        System.out.println(list.contains(1));
        for (Integer integer : list) {
            System.out.print(integer + " ");
        }
    }

LinkedList 具有一些特殊的操作

LinkedList 底层是链表来实现的, 所以他在查找的时候是比较慢的, 但是在删除元素和添加元素的时候会快一点。 由于 LinkedList 提供了一些对于头部尾部元素操作的API, 所以完全可以当作栈和队列来用。

  • 链表查找元素 只能一个一个遍历
  • 链表删除、添加元素 改变节点的指针指向即可
    private static void testLinkList(LinkedList<Integer> linkedList) {
        linkedList.push(2);
        /**
         * 返回头元素 、尾部元素,  添加 头、尾部元素
         */
        System.out.println(linkedList.getFirst());
        System.out.println(linkedList.getLast());
        linkedList.addFirst(2);
        linkedList.addLast(3);

        linkedList.removeLast();


        /**
         * 返回头元素 、尾部元素, 但是不删除
         */
        Integer peek = linkedList.peek();
        System.out.println(peek);
        Integer integer = linkedList.peekFirst();
        System.out.println(integer);
        Integer peekLast = linkedList.peekLast();
        System.out.println(peekLast);
        /**
         * 返回头元素 、尾部元素,  并且删除元素
         */
        Integer poll = linkedList.poll();
        System.out.println(poll);
        Integer pollFirst = linkedList.pollFirst();
        System.out.println(pollFirst);
        Integer pollLast = linkedList.pollLast();
        System.out.println(pollLast);
    }

Vector

Vector 也是 由数组来完成底层实现的集合, 相应着它也是一个有序的集合, 并且由于 自身的方法都被synchronized 修饰过了,所以它是一个线程安全的!

  • 数组底层实现
  • synchronized 线程安全
    public synchronized int capacity() {
        return elementData.length;
    }

Map

在这里插入图片描述

我们常用的 Map中继承关系

  • Map key 不可重复, value可以重复
    • HashMap
      • 线程不安全
      • 底层数据 + 链表 + 红黑树
      • 允许key一次为空
    • HashTable
      • 线程安全
      • 底层哈希表
      • key value都不能是空
    • TreeMap
      • 线程不安全
      • 采用二叉树

HashTable

HashTable 线程安全的原因, 方法都用了 synchronized 修饰!

    public synchronized int size() {
        return count;
    }

TreeMap

TreeMap 是SortedMap的子类, 所以它是一个有序的, 就是在存放映射关系的时候, 会安装key进行一个排序。

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
}

public interface NavigableMap<K,V> extends SortedMap<K,V> {}

并发问题

什么是并发 和并行

并发的含义

  • 在只有一个处理器的机器上运行的多个线程
  • 多个线程按照时间片轮转的方式交替执行
  • 线程A 在执行任务
  • 线程B 也在执行任务
  • 线程A 操作一半的时候, 时间片轮转了
  • 线程B 开执行自己的任务
  • 并发中 线程之间是在抢占资源的

并行的含义

  • 在多个处理器额机器上运行多个线程
  • 一个时间点
  • 处理器 一 在执行任务A
  • 处理器 二 在执行任务 B
  • 并行 中线程不需要抢占资源

小结

单核的CPU 由于每个时间点只能执行一个进程, 但是采用进程调度算法之后使得线程之间可以交替执行, 从而造成一种多个进程可以同时执行的假象, 这就是并发!

多核的CPU在执行进程的时候, 可以一个核心来执行一个进程, 这样就是进程之间的并行! 但是当进程的数量大于核数的时候就会变成并发了!

  • 两核、A B C 三个进程
  • 核 一 执行 A
  • 核 二 执行 B
  • 此时 进程A B 之间在并行!
  • 核一 停止的 A 的执行 、 开始执行C
  • 此时 A C 就是处于一个并发!

为什么并发下不安全

下面的测试程序, 对于sum 的累加操作由10个线程来执行,但是最终的执行结果每次都不相同, 原因就是并发。

  • 首先sum++ 不是一个原子的操作, 会转化为若干条的机器指令, 在执行++的时候也许会被打断, 原子性。
  • 线程之间的数据共享问题, 线程A 修改了数据, 线程B 不能立马知道最新的数据, 可见性。
  • CPU在执行的时候并没有按照原有程序的顺序执行, 指令重排。

解决的办法就是围绕着上面的原因来处理! 变成原子操作, 保证可见性, 禁止指令重排, 或者加锁, 让线程逐个执行!

public class Test {
    static Lock lock = new ReentrantLock();

    static Integer value =0;

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                add();
            }, "" + i).start();
        }

 		// 保证上面的线程都已经执行完毕了
        TimeUnit.SECONDS.sleep(5);
        System.out.println(Test2.value);
    }


    /**
     *
     */
    public static void add() {
        for (int i = 0; i < 10000; i++) {
                Test2.value++;
        }
    }
}

使用锁

  • synchronized
  • Lock
    public synchronized static void add2() {
        for (int i = 0; i < 10000; i++) {
            Test2.value++;
        }
    }

    public static void add3() {
        for (int i = 0; i < 10000; i++) {
            synchronized (Test2.value) {
                Test2.value++;
            }
        }
    }

    public static void add4() {
        for (int i = 0; i < 10000; i++) {
            lock.lock();
            try {
                Test2.value++;
            } catch (Exception e) {
            } finally {
                lock.unlock();
            }
        }
    }

不安全集合

List

解决办法

  • vector
  • synchronizedList
  • CopyOnWriteArrayList
// 测试 list 的线程不安全
public class TestList {
    // Exception in thread "6" java.util.ConcurrentModificationException
    public static void main(String[] args) {
        List<Integer> list;
        list = new ArrayList<Integer>();
        //  list = new Vector<>();
        /**
         * 解决方案
         * 1、调用集合工具包 Collections.synchronizedList();
         * 2、使用 vector
         * 3、new CopyOnWriteArrayList<Integer>();
         */
        List<Integer> integerList = Collections.synchronizedList(list);
        // list = new CopyOnWriteArrayList<Integer>();
        //
        for (Integer i = 0; i < 30; i++) {
            Integer temp = i;
            new Thread(() -> {
                list.add(temp);
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

Set

  • synchronizedSet
  • CopyOnWriteArraySet
// Exception in thread "43" java.util.ConcurrentModificationException
public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
/**
 * 1、Collections.synchronizedSet(set);
 * 2、new CopyOnWriteArraySet<>();
 */
        Set<String> set1 = Collections.synchronizedSet(set);
        CopyOnWriteArraySet<Object> set2 = new CopyOnWriteArraySet<>();
        for (int i = 0; i <
                50; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set.toString());
            }, String.valueOf(i)).start();
        }
    }
}

Map

  • synchronizedMap
  • ConcurrentHashMap
public class MapTest {

    public static void main(String[] args) {
        Map<Object, Object> map;
        // map = new HashMap<>();
        //  Exception in thread "11" java.util.ConcurrentModificationException
        // map = Collections.synchronizedMap(new HashMap<>());
        map = new ConcurrentHashMap();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(UUID.randomUUID().toString().substring(0, 2),
                        UUID.randomUUID().toString().substring(0, 2));

                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}

小结

解决集合线程安全的方法还是按照线程安全的原因来处理的! !

  • 加锁来保证同一个时刻只有一个线程可以操作这个资源。
    • synchronized
    • Lock
  • 使用其他的手段来保证线程之间数据的可见性问题。
    • CopyOnWrite
    • ConcurrentHashMap
      • 内存分段

synchronized

synchronized

synchronized 本身是一个关键字, 底层使用的是阻塞队列。可以在并发环境下来保证数据的一致性。主要的特点。

  • 不需要显示的解锁和加锁
  • 基于阻塞对列, 可能会造成线程的阻塞
  • 可以作用与同步方法以及需要同步的资源
  • 锁住的是资源的使用者
    • 同步方法来说, 锁住的就是方法的调用者
    • 对于需要同步的资源来说, 锁住的就是该资源的使用者

CopyOnWrite

CopyOnWrite

写入时复制思想, 当多个线程同时访问系统资源的时候, 他们都会得到该资源的指针。当出现一个线程要修改该资源的时候, 系统会给该线程一个资源的备份, 此时系统中该资源并没有做出修改, 当线程修改完该资源的时候会用新的数据来替换系统中旧的数据, 在此期间其他线程依旧可以读取该资源,但是读取到的都是旧的数据。

  • A 修改 资源 a , B 读取
  • A 得到资源a的副本 进行修改, B 继续读取资源 a
  • A修改结束后,用新的资源a 替换 之前的 资源a
  • 这样的操作步骤, 可以在最终的时候保证数据的一致性,在替换旧资源的时候也会出现数据的不一致。

合适场景

  • 读多 写少的情况, 读取的时候都能得到最终一致的数据, 并且是一种没有使用锁的机制

  • 和读写锁存在着区别

    • 读写锁是一种细粒度的锁, 在发生读写互斥的情况下会加锁
  • https://www.cnblogs.com/jmcui/p/12377081.html#_label0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值