常见的同步容器类包括Vector和HashTable,以及Collections.synchronizedXxx()等工厂方法。这些类实现线程安全的方式是:将他们的可变成员变量封装起来,并对每个方法都进行同步,使得每次仅仅有一个线程能访问这些可变的成员变量。尽管这些类的方法都是同步的,但当并发访问多个方法的时候,还是有可能出错。比如,有两个线程,一个执行同步的get方法,一个执行同步的remove方法,那么这两个线程仍然可能出现竞态条件(多个线程不同的时序导致程序出问题):“先remove在get就会出现问题”,在使用的时候仍然要注意。
这里要特别说一个异常的情况--ConcurrentModificationException,这个异常是程序在迭代的过程中被修改的时候就会出现。看下面的例子:
在上面的线程执行的过程中,有另外一个线程获得了CPU,执行了remove()方法,此时就会报告ConcurrentModificationException。
有些时候,我们并没有显示的迭代,带不代表我们没有进行迭代,如下:
在最后一行的代码中,实际上调用了vector的toString()函数,这个函数就是一个隐藏的迭代过程。如果在这个过程中,一个线程获得了CPU并且执行了remove()方法,也会报告ConcurrentModificationException异常。
但是不管怎样,同步容器能在相当大的程度上保证线程安全。甚至对于大多数情况下,同步容器有点过于安全,以至于牺牲了性能。这时候,并发容器出现了。并发容器通过使用比同步容器更加细粒度的锁,在安全性没有太大降低的情况下又提升了并发时候的性能。正如书上《JAVA并发编程实践》有句话说的,通过并发容器来代替同步容器,可以极大的提高伸缩性并且降低安全性风险。
关于同步容器和并发容器更加深入的探讨在后续的博客进行,但这里我们要记住一句话:多线程编程中,在保证安全性的前提下,能使用并发容器就不要用同步容器。
这里要特别说一个异常的情况--ConcurrentModificationException,这个异常是程序在迭代的过程中被修改的时候就会出现。看下面的例子:
Vector v=new Vector();
for(int i=0;i<v.size();i++)
doSomething(v.get(i));
在上面的线程执行的过程中,有另外一个线程获得了CPU,执行了remove()方法,此时就会报告ConcurrentModificationException。
有些时候,我们并没有显示的迭代,带不代表我们没有进行迭代,如下:
Vector v=new Vector();
System.out.println(v);
在最后一行的代码中,实际上调用了vector的toString()函数,这个函数就是一个隐藏的迭代过程。如果在这个过程中,一个线程获得了CPU并且执行了remove()方法,也会报告ConcurrentModificationException异常。
但是不管怎样,同步容器能在相当大的程度上保证线程安全。甚至对于大多数情况下,同步容器有点过于安全,以至于牺牲了性能。这时候,并发容器出现了。并发容器通过使用比同步容器更加细粒度的锁,在安全性没有太大降低的情况下又提升了并发时候的性能。正如书上《JAVA并发编程实践》有句话说的,通过并发容器来代替同步容器,可以极大的提高伸缩性并且降低安全性风险。
关于同步容器和并发容器更加深入的探讨在后续的博客进行,但这里我们要记住一句话:多线程编程中,在保证安全性的前提下,能使用并发容器就不要用同步容器。