1.简述
同步容器可以简单的理解为通过synchronized来实现同步的容器(因为使用了synchronized关键字所以在性能方面没有线程不安全的容器好),如果有多个线程调用同步容器的方法,它们将会串行执行。包括Vector和Hashtable,以及由同步容器封装类。Collections.synchronizedXxx等工厂方法创建的类。
在Java中,同步容器主要包括2类:
- Vector、Stack、HashTable类。
- Collections类中提供的静态工厂方法创建的类(例如Collections.synchronizedList(new ArrayList<String>()))。
2.同步容器的存在的并发问题
单独使用同步容器所提供的方法操作,不会带来任何的并发问题。因为同步逻辑已经被封装在该操作对应的方法中。但如果使用复合操作,很有可能带来并发问题,复合操作的同步逻辑需要你自己去实现。常见的复合操作有:迭代(容器遍历)、跳转(根据指定顺序找到当前元素的下一个元素)和检查执行(先检查某一条件,该条件满足了再执行指定操作。如:若没有则添加)。Java 中,对同步容器的复合操作可能会产生异常,最常见的异常有:ArrayIndexOutOfBoundsException和ConcurrentModificationException。
(1)ArrayIndexOutBoundsException异常
ArrayIndexOutOfBoundsException异常代码如下:
public class Test{ public static void main(String[] args) { //添加 10000 个元素到容器 for(int i = 0; i < 10000; i++) vector.add(i); //启动 N个线程执行删除操作 for(int i = 0; i < 200; i++) { new Thread(new Runnable() { @Override public void run() { deleteLast(vector);//删除最后一个元素 } }).start(); } System.out.println("end"); } //创建一个 Vector static Vector<Integer> vector = new Vector<Integer>(); //定义删除最后一个元素的方法 public static void deleteLast(Vector<Integer> list) { int lastindex = list.size() - 1; list.remove(lastindex); } }
执行结果是抛出异常java.lang.ArrayIndexOutOfBoundsException(数组下标越界)。在这个多线程程序中,Vector的size是不断减小的,可能一个线程已经把元素A删除了,另一个线程再去把元素A删除一遍,但是元素A已经不存在了,所以抛出异常。这种问题可以通过客户端加锁来解决。
ArrayIndexOutOfBoundsException异常解决代码如下:
public class Test{ public static void main(String[] args) { //添加 10000 个元素到容器 for(int i = 0; i < 10000; i++) vector.add(i); //启动 N个线程执行删除操作 for(int i = 0; i < 200; i++) { new Thread(new Runnable() { @Override public void run() { synchronized (vector){//加入同步锁 deleteLast(vector);//删除最后一个元素 } } }).start(); } System.out.println("end"); } //创建一个 Vector static Vector<Integer> vector = new Vector<Integer>(); //定义删除最后一个元素的方法 public static void deleteLast(Vector<Integer> list) { int lastindex = list.size() - 1; list.remove(lastindex); } }
通过在调用删除方法前加入synchronized关键字,执行已经不会抛出异常了。
(2)ConcurrentModificationException异常
ConcurrentModificationException异常代码如下:
public class Test { public static void main(String[] args) throws Exception { //添加 100 个元素到容器 for(int i = 0; i < 100; i++) { vector.add(i); } //创建一个线程,遍历 Vector new Thread(new Runnable() { @Override public void run() { Iterator<Integer> it = vector.iterator(); while(it.hasNext()) { Integer integer = (Integer) it.next(); //调用next try { Thread.sleep(100); //睡眠 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }).start(); //创建一个线程,遍历 Vector new Thread(new Runnable() { @Override public void run() { //利用迭代器遍历,在遍历的同时删除一个元素 Iterator<Integer> it = vector.iterator(); while(it.hasNext()) { Integer integer = (Integer) it.next(); if(integer == 5) { it.remove(); //删除一个元素,更新变量 modCount,expectedModCount的值。 } } } }).start(); } static Vector<Integer> vector = new Vector<Integer>(); //创建一个 Vector }
执行结果是抛出异常java.util.ConcurrentModificationException。在这个多线程程序中,Vector等同步容器进行并发迭代修改的时候,就会出现这个异常。因为it.next()方法会检查这两个变量(modCount、expectedModCount)是否相等,不等则抛出这个异常。直接调用v.remove(),它会更新 modCount 的值,却没有更新 expectedModCount 的值,所以抛出异常。这种问题可以在使用iterator迭代的时候加锁来解决。
ConcurrentModificationException异常解决代码如下:
public class Test { public static void main(String[] args) throws Exception { //添加 100 个元素到容器 for(int i = 0; i < 100; i++) { vector.add(i); } //创建一个线程,遍历 Vector new Thread(new Runnable() { @Override public void run() { synchronized(vector) { Iterator<Integer> it = vector.iterator(); while(it.hasNext()) { Integer integer = (Integer) it.next(); //调用next try { Thread.sleep(100); //睡眠 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }).start(); //创建一个线程,遍历 Vector new Thread(new Runnable() { @Override public void run() { synchronized(vector) { //利用迭代器遍历,在遍历的同时删除一个元素 Iterator<Integer> it = vector.iterator(); while(it.hasNext()) { Integer integer = (Integer) it.next(); if(integer == 5) { it.remove(); //删除一个元素,更新变量 modCount,expectedModCount的值。 } } } } }).start(); } static Vector<Integer> vector = new Vector<Integer>(); //创建一个 Vector }
通过在使用iterator迭代前加入synchronized关键字,执行已经不会抛出异常了。