51dev.com IT技术开发者社区

51dev.com 技术开发者社区

ArrayList,Vector线程安全性测试

代码星球阅读(21)2020-05-24 收藏0次评论

结论:如果集合不是线程安全的话,在多线程情况下插入数据会出现数据丢失的问题。

 

    import java.util.ArrayList;  
    import java.util.List;  
      
    //实现Runnable接口的线程  
    public class HelloThread implements Runnable {  
        String name;  
        List<String> v;  
      
        HelloThread(String name, List<String> v) {  
            this.name = name;  
            this.v = v;  
        }  
      
        public void run() {  
            System.out.println(name + "start");  
            while(true) {  
                v.add(name + ".add");  
                System.out.println(name + " list size is " + v.size());  
      
                try {  
                    Thread.sleep(10);  
                } catch(InterruptedException e) {  
                    System.out.println(e.getMessage());  
                }  
            }  
        }  
      
        public static void main(String args[]) throws InterruptedException {  
      
            List<String> v = new ArrayList<String>();  
      
            HelloThread hello1 = new HelloThread("hello1", v);  
            HelloThread hello2 = new HelloThread("hello2", v);  
            HelloThread hello3 = new HelloThread("hello3", v);  
      
            Thread h1 = new Thread(hello1);  
            Thread h2 = new Thread(hello2);  
            Thread h3 = new Thread(hello3);  
            h1.start();  
            h2.start();  
            h3.start();  
      
        }  
    }  

 

 结果:

hello3start
hello3 list size is 1
hello1start
hello1 list size is 2
hello2start
hello2 list size is 3
hello3 list size is 4 
hello1 list size is 5
hello2 list size is 4 
hello3 list size is 6
hello1 list size is 8
hello2 list size is 7
hello1 list size is 9 
hello3 list size is 10
hello2 list size is 9

 

加了12次,但size却只有10,不安全

 

改成号称线程安全的Vector:

    import java.util.Vector;  
      
    //实现Runnable接口的线程  
    public class HelloThread implements Runnable {  
        String name;  
        Vector<String> v;  
      
        HelloThread(String name, Vector<String> v) {  
            this.name = name;  
            this.v = v;  
        }  
      
        public void run() {  
            System.out.println(name + "start");  
            while(true) {  
                v.add(name + ".add");  
                System.out.println(name + " vector size is " + v.size());  
      
                try {  
                    Thread.sleep(10);  
                } catch(InterruptedException e) {  
                    System.out.println(e.getMessage());  
                }  
            }  
        }  
      
        public static void main(String args[]) throws InterruptedException {  
      
            Vector<String> v = new Vector<String>();  
      
            HelloThread hello1 = new HelloThread("hello1", v);  
            HelloThread hello2 = new HelloThread("hello2", v);  
            HelloThread hello3 = new HelloThread("hello3", v);  
      
            Thread h1 = new Thread(hello1);  
            Thread h2 = new Thread(hello2);  
            Thread h3 = new Thread(hello3);  
            h1.start();  
            h2.start();  
            h3.start();  
        }  
    }  

 

结果:

hello1start
hello1 vector size is 1
hello2start
hello2 vector size is 2
hello3start
hello3 vector size is 3
hello1 vector size is 4
hello2 vector size is 5
hello3 vector size is 6
hello1 vector size is 7
hello2 vector size is 8
hello3 vector size is 9
hello1 vector size is 10
hello2 vector size is 11
hello3 vector size is 12
hello1 vector size is 13
hello3 vector size is 15
hello2 vector size is 15 
hello1 vector size is 16

也出现了线程不安全现象吗?不是的

这个不算线程不安全,加了16次,size是16,恰恰是线程安全的表现,只不过是待两个线程都add完了之后才调的size(),所以都是15,跳过了14。

以上一样的程序多试几次就出现了,另外关于Vector的thread-safety

All Vector methods are synchronized themselves, so as long as you are only synchronizing around a single method, your own synchronization is not necessary. If you have several method calls, which depend on each other, e.g. something like vec.get(vec.size()-2) to get the second last element, you have to use your own synchronization since otherwise, the vector may change between vec.size() and vec.get().

 

所有的Vector的方法对它们自己而言都是 synchronized的,所以如果只要同步单个方法,自己额外添加的同步措施就失去必要了。如果有几个方法需要调用,且它们互相存在依赖,比如 vec.get(vec.size()-2),是要得到倒数第二个元素,那么就必须加上自己的同步措施,因为否则的话,vector有可能在 vec.size() 和 vec.get() 之间发生改变

 

再补充一下:
Vector 是否是线程安全的?因为框架大量使用 RMI,RMI 是天生非线程安全的,所以作者认为采用了 Vector 来声明成员变量后,类就是 Thread-safe 了。
或许,大家经常也碰到类似的问题:Vector 与 ArrayList 的区别?好多人一拍脑门就出:Vector 是线程安全的 (在任何情况下都是)。。。
原因可能是因为 Vector 的所有方法加上了 synchronized 关键字,从而保证访问 vector 的任何方法都必须获得对象的 intrinsic lock (或叫 monitor lock),也即,在vector内部,其所有方法不会被多线程所访问。
但是,以下代码呢:

if (!vector.contains(element))
    vector.add(element);
    ...
}

这是经典的 put-if-absent 情况,尽管 contains, add 方法都正确地同步了,但作为 vector 之外的使用环境,仍然存在  race condition: 因为虽然条件判断 if (!vector.contains(element))与方法调用 vector.add(element);  都是原子性的操作 (atomic),但在 if 条件判断为真后,那个用来访问vector.contains 方法的锁已经释放,在即将的 vector.add 方法调用 之间有间隙,在多线程环境中,完全有可能被其他线程获得 vector的 lock 并改变其状态, 此时当前线程的vector.add(element);  正在等待(只不过我们不知道而已)。只有当其他线程释放了 vector 的 lock 后,vector.add(element); 继续,但此时它已经基于一个错误的假设了。

单个的方法 synchronized 了并不代表组合(compound)的方法调用具有原子性,使 compound actions  成为线程安全的可能解决办法之一还是离不开intrinsic lock (这个锁应该是 vector 的,但由 client 维护):

// Vector v = ...
    public  boolean putIfAbsent(E x) {
synchronized(v) {
            boolean absent = !contains(x);
            if (absent) {
                add(x);
}
}
        return absent;
    }

所以,正确地回答那个“愚蠢”的问题是:
Vector 和 ArrayList 实现了同一接口 List, 但所有的 Vector 的方法都具有 synchronized 关键修饰。但对于复合操作,Vector 仍然需要进行同步处理。

这样做的后果,Vector 应该尽早地被废除,因为这样做本身没有解决多线程问题,反而,在引入了概念的混乱的同时,导致性能问题,因为 synchronized 的开销是巨大的:阻止编译器乱序,hint for 处理器寄存一/二级缓存。。。

 

以上就是ArrayList,Vector线程安全性测试的全部内容。