互联网技术08——同步类容器

集合容器框架关系简介:

  1. 在Java集合容器框架中,主要有四大类:list、set、queue、map。期中list、set、queue都是继承了collection接口。Map本身是一个接口。
  2. 注意collection和map是一个顶层接口,而list、set、queue继承collection,分别带边数组、集合、队列这三大类容器。ArrayList和linkedList实现了list接口,hashSet实现了set接口,而deque(双向队列)继承了queue接口。priorityQueue实现了Queue。另外linkedList实际上实现了Deque接口。
  3. 像ArrayList和LinkList以及hshMap这些都是非线程安全的,当多个线程同时访问时,就会有线程安全问题。如果我们为了应对多线程开发而去手动处理,这样很不方便。

同步容器介绍:

  1. 在java中,主要包含两类同步容器  Vector、Stack、HashTable
  2. 使用Collectionns类中提供的静态工厂方法创建的类
  • Vector实际上是实现了List接口,,但是Vector类中,方法通过使用synchronized修饰,实现同步的目的
  • Stack实际上是继承了Vector类,它的方法也通过synchronized同步。
  • hashTable实现了map接口,他和hashMap很相似,但是他进行了同步处理,hashMap没有

collections工具类

collections是一个工具类,和collection不同,collection是一个顶层接口。collections提供了大量的方法,例如对集合的排序、查找等。如图,可以发现,collections提供了多个静态工厂方法类创建同步类容器。

思考:

  1. 通过synchronized修饰后,线程性能一定会受到影响

 public static void main(String[] args) throws InterruptedException {
            ArrayList<Integer> list = new ArrayList<Integer>();
            Vector<Integer> vector = new Vector<Integer>();
            long start = System.currentTimeMillis();
            for(int i=0;i<100000;i++)
                list.add(i);
            long end = System.currentTimeMillis();
            System.out.println("ArrayList进行100000次插入操作耗时:"+(end-start)+"ms");
            start = System.currentTimeMillis();
            for(int i=0;i<100000;i++)
                vector.add(i);
            end = System.currentTimeMillis();
            System.out.println("Vector进行100000次插入操作耗时:"+(end-start)+"ms");
        }

运行结果

ArrayList进行100000次插入操作耗时:18ms
Vector进行100000次插入操作耗时:44ms

可以看见,vector的效率已经大打折扣了。如果是多线程的访问,不但涉及到线程等待,还涉及到锁竞争产生的性能损耗问题。

      2.             通过synchronized修饰的就一定是线程安全的么?

package com.company;

import java.util.Vector;

/**
 * Created by BaiTianShi on 2018/8/17.
 */
public class CollectionAndMapChildren {
    static Vector<Integer> vector = new Vector<Integer>();
    public static void main(String[] args) throws InterruptedException {
        while(true) {
            for(int i=0;i<10;i++)
                vector.add(i);
            Thread thread1 = new Thread(){
                public void run() {
                    for(int i=0;i<vector.size();i++)
                        vector.remove(i);
                };
            };
            Thread thread2 = new Thread(){
                public void run() {
                    for(int i=0;i<vector.size();i++)
                        vector.get(i);
                };
            };
            thread1.start();
            thread2.start();
            while(Thread.activeCount()>10)   {

            }
        }
    }


}

运行结果

Exception in thread "Thread-725" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 11
	at java.util.Vector.get(Vector.java:748)
	at com.company.CollectionAndMapChildren$2.run(CollectionAndMapChildren.java:23)
Exception in thread "Thread-12123" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 29
	at java.util.Vector.get(Vector.java:748)
	at com.company.CollectionAndMapChildren$2.run(CollectionAndMapChildren.java:23)
  • 抛出ArrayIndexOutOfBoundsException,数组越界异常。这是由于当某个线程A执行在读取vector.size()时,假设返回的是10,循环到9。同时其他某个线程B正好执行了remove的操作,然后A继续执行get(9)操作,就出现了数组下标越界异常。
  • 进一步解释一下:不论是同一个线程之间还是多个线程之间,它们在执行vector.size()、remove()等方法时都是需要单独去竞争锁,假设A线程需要执行vector.size(),则它需要竞争得到vector的锁,但是执行完vector.size()后马上释放了vector的锁,而不会去等待for循环中的get方法执行完(此处一定要分清,这和重入锁不是一回事,重入锁没有释放锁的过程,而是将state状态加1,这里显然不是),这样问题就出来了,一旦vector.size()执行完后释放锁,其他等待的任何线程都有机会获得这个vector的锁。假设其他线程执行了remove操作,集合长度就小于10了。当A线程再次获得vector锁时,执行get(10)级一定会报数组越界错。
  • 解决办法 :将每套循环锁住,不被拆分
    public class Test {
        static Vector<Integer> vector = new Vector<Integer>();
        public static void main(String[] args) throws InterruptedException {
            while(true) {
                for(int i=0;i<10;i++)
                    vector.add(i);
                Thread thread1 = new Thread(){
                    public void run() {
                        synchronized (Test.class) {   //进行额外的同步
                            for(int i=0;i<vector.size();i++)
                                vector.remove(i);
                        }
                    };
                };
                Thread thread2 = new Thread(){
                    public void run() {
                        synchronized (Test.class) {
                            for(int i=0;i<vector.size();i++)
                                vector.get(i);
                        }
                    };
                };
                thread1.start();
                thread2.start();
                while(Thread.activeCount()>10)   {
                     
                }
            }
        }
    }
    

同步类容器,这些容器的同步功能都是由JDK的Collection.synchronized***等工厂方法创建的。其底层无非就是用传统的synchronized关键字对每个公用的方法进行同步,使得每次只能一个线程访问容器。但是在性能方面,并不能很友好的支持高并发的要求。

猜你喜欢

转载自blog.csdn.net/qq_28240551/article/details/81751158