前言
前言点击此处查看:
http://blog.csdn.net/wang7807564/article/details/79113195
同步容器
问题引出:
有N张火车票,每张票都有一个编号,同时有10个窗口对外售票,写一个模拟程序。
public class TicketSeller {
static List<String> tickets = new ArrayList<>();
static {
for(int i=0; i<10000; i++) tickets.add("票编号:" + i);
}
public static void main(String[] args) {
for(int i=0; i<10; i++) {
new Thread(()->{
while(tickets.size() > 0) {
System.out.println("销售了--" + tickets.remove(0));
}
}).start();
}
}
}
在上述程序运行中,会报错,数组越界:
java.lang.ArrayIndexOutOfBoundsException
很显然,这是因为多线程之间没有处理好同步问题造成的。
Vector
尝试将tickets换为vector类型:
static Vector<String> tickets = new Vector<>();
List接口一共有三个实现类,分别是ArrayList、Vector和LinkedList.
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性。它的内部是使用synchronized关键字来实现同步锁的。
虽然在笔者测试中没有没有出现过数组越界的问题,但是使用Vector是具有潜在的线程不安全风险的。原因是vector对每个读取和写入的方法是单独加锁的,也就是说使用synchronized关键字修饰了读取和写入的方法。但是在上面的例子中,读取和写入应该作为一个整体,这个整体应该保证原子性,单独的读取和写入虽然加锁,但是这个整体仍然是线程不安全的,这个整体对应的就是while循环的代码块。如果想要实现绝对的线程安全,可以改成如下例子,但是是牺牲性能的:
new Thread(()-> {
synchronized (new Object()) {
{
while (tickets.size() > 0) {
System.out.println("销售了--" + tickets.remove(0));
}
}
}
}).start();
Collections.synchronizedXXX():
Collections类是一个工厂类,通过这个工厂类来获取一些同步容器的实例。
这些通过工厂方法获取到的实例,内部也是通过synchronized关键字来完成同步的。
Synchronizedxxx方法有两个不足:
1. 首先,这种方法对于可伸缩性是一种障碍,因为一次只能有一个线程可以访问hash表。
2. 同时,这样仍不足以提供真正的线程安全性,许多公用的混合操作仍然需要额外的同步。
使用下面的代码,也能达到解决该问题的目的:
static List<String> tickets = Collections.synchronizedList(new ArrayList<>());
在获取tickects实例的时候,使用collections.synchronizedxxx()函数进行同步,这样可以获取一个同步容器的实例。避免了数组越界问题。
对于上述问题,如果使用synchronized关键字也可以实现:
While(true)
Synchronized(tickets)
{
判断size
Remove()移除
}
这里面相当于将size()和remove()两个原子操作合并为一个原子操作,实现了读写锁。但是,这种方法相对来说,更占用CPU资源。
虽然诸如get()和put()之类的简单操作可以在不需要额外同步的情况下安全地完成.但还是有一些公用的操作序列,例如迭代操作或者put-if-absent(空则放入),需要外部的同步,以避免数据争用。
synchronizedMap、synchronizedList等也被称为有条件的线程安全同步的集合包装器:
也就是说所有单个的操作都是线程安全的.但是多个操作组成的操作序列却可能导致数据争用,因为在操作序列中控制流取决于前面操作的结果。
所以,在JDK 5版本以后,对于高并发场景推荐使用ConcurrentHashMap.其特点:效率比Hashtable高,并发性比hashmap好,HashMap中未进行同步考虑,而Hashtable则使用了synchronized,带来的直接影响就是可选择,我们可以在单线程时使用HashMap提高效率,而多线程时用Hashtable来保证安全,而ConcurrentHashMap则结合了两者的特点。