欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-11 11:53:32
...

集合容器框架关系简介:

  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. 互联网技术08——同步类容器
  4. 像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提供了多个静态工厂方法类创建同步类容器。

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

思考:

  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关键字对每个公用的方法进行同步,使得每次只能一个线程访问容器。但是在性能方面,并不能很友好的支持高并发的要求。

相关标签: 多线程