互联网技术08——同步类容器
程序员文章站
2022-07-11 11:53:32
...
集合容器框架关系简介:
- 在Java集合容器框架中,主要有四大类:list、set、queue、map。期中list、set、queue都是继承了collection接口。Map本身是一个接口。
- 注意collection和map是一个顶层接口,而list、set、queue继承collection,分别带边数组、集合、队列这三大类容器。ArrayList和linkedList实现了list接口,hashSet实现了set接口,而deque(双向队列)继承了queue接口。priorityQueue实现了Queue。另外linkedList实际上实现了Deque接口。
- 像ArrayList和LinkList以及hshMap这些都是非线程安全的,当多个线程同时访问时,就会有线程安全问题。如果我们为了应对多线程开发而去手动处理,这样很不方便。
同步容器介绍:
- 在java中,主要包含两类同步容器 Vector、Stack、HashTable
- 使用Collectionns类中提供的静态工厂方法创建的类
- Vector实际上是实现了List接口,,但是Vector类中,方法通过使用synchronized修饰,实现同步的目的
- Stack实际上是继承了Vector类,它的方法也通过synchronized同步。
- hashTable实现了map接口,他和hashMap很相似,但是他进行了同步处理,hashMap没有
collections工具类
collections是一个工具类,和collection不同,collection是一个顶层接口。collections提供了大量的方法,例如对集合的排序、查找等。如图,可以发现,collections提供了多个静态工厂方法类创建同步类容器。
思考:
-
通过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关键字对每个公用的方法进行同步,使得每次只能一个线程访问容器。但是在性能方面,并不能很友好的支持高并发的要求。
上一篇: 互联网技术点评:云计算点评(一)
下一篇: 一个例子理解函数声明与函数表达式的区别