同步容器类--Vector--并不是绝对的线程安全

(一)同步容器类

1.  同步容器的分类:

       a.  早期JDK的同步容器类包括Vector和Hashtable;

       b.  JDK1.2种添加的一些功能相似的类,这些同步的封装器类是由Collections.synchronizedXxx等工厂方法创建。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。

2.  同步容器的问题--单个公有方法都是同步的(具有原子性),但有些复合操作不具有原子性,使用时应小心

       在Vector中,线程安全指的是:增删改查方法都是原子性的,在同一时刻,只能由一个线程访问,而复合操作是非原子性的,当多个线程交叉访问不同的同步方法时,会出现线程非安全的情况;

       如下代码演示了会出问题的复合操作:向vector放入5个不重复的元素,放入元素前先判断集合中是否已经存在--使用contains方法,如果不存在则加入--使用add方法,其中contains方法和add方法都是线程同步的,并给出了在JDK中的源码:

  Vector中的contains源码:

 public boolean contains(Object o) {
        return indexOf(o, 0) >= 0;
 }
 public synchronized int indexOf(Object o, int index) {
        if (o == null) {
            for (int i = index ; i < elementCount ; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = index ; i < elementCount ; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
 }

Vector中的add源码:

 public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

 

1. package test;
2. import java.util.Vector;
3. import java.util.concurrent.CyclicBarrier;
4. public class MyVector2 {
5.     final static  Vector<String> vector = new Vector<String>();
6. 	final static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
7.     public static void main(String[] args) throws InterruptedException {
8.     	testThread();//启动两个线程同时往容器中添加元素
9.     	Thread.sleep(100);//暂停100毫秒,等待两个线程执行完
10.     	vector.sort(null);
11. 		for(int j = 0; j < vector.size(); j++){
12.     		System.out.println("vector.size() = " + vector.size() + " , " + vector.get(j));
13.     	}
14. 		System.out.println(Thread.currentThread().getName() + ",執行完毕。");
15.     }
16.     public static void testThread(){
17.     	new Thread(new Runnable(){
18.     		public void run(){
19.     			addNumber();
20.     		}
21.     	},"t1").start();;
22.     	new Thread(new Runnable(){
23.     		public void run(){
24.     			addNumber();
25.     		}
26.     	},"t2").start();
27.     }
28.     
29.     public static void addNumber()
30.     {
31.     	System.out.println(Thread.currentThread().getName() + ",到达");
32.     	try {
33. 			cyclicBarrier.await();//使用栅栏,当两个线程都到达后,一起往容器中添加元素
34. 		} catch (Exception e) {
35. 			e.printStackTrace();
36. 		} 
37.     	System.out.println(Thread.currentThread().getName() + ",开始执行");
38.     	for(int i = 0; i < 5; i++){
39.     		String str = i + "";
40.     			if(!vector.contains(str))
41.         		{
42.         			System.out.println(Thread.currentThread().getName() + ",str = " + str);
43.         			vector.add(str);
44.         		}
45.     	}
46.     }
47. }

其中一次地运行结果如下:

t1,到达
t2,到达
t1,开始执行
t2,开始执行
t1,str = 0
t2,str = 0
t1,str = 1
t1,str = 2
t1,str = 3
t1,str = 4
t2,str = 3
vector.size() = 7 , 0
vector.size() = 7 , 0
vector.size() = 7 , 1
vector.size() = 7 , 2
vector.size() = 7 , 3
vector.size() = 7 , 3
vector.size() = 7 , 4
main,執行完毕。

根据代码的输出结果,我做了如下分析:

        a. 在代码中使用了栅栏,当两个线程t1和t2都到达第33行时,这两个线程开始执行第38行的for循环;

        b. 第一次循环:由于第40行的方法contains是同步方法,在同一时刻只能有一个线程在执行,根据输出得知,t1先获取到的contains方法的执行权,t2被阻塞;当线程t1执行完第40行代码,而没执行第43行代码时,中间会有一个非常短的时间,在这个非常短的时间会发生如下事情:此时容器中是空的,线程t1执行contains方法后返回true,线程t1释放锁,线程t2获取锁,线程t2获取锁之后,接着执行contains方法,由于此时容器中没有元素0,所以也会返回true;线程t2释放锁之后,线程t1要开始执行add方法,线程t1在执行add方法之前,必须先获取锁,然后才能执行add方法,线程t1在执行完add方法后释放锁,此时容器中就有了个元素0;线程t2这时候开始执行add方法,由于在执行contains方法时,元素0还不在容器中,所以线程t2也会把元素0放入到容器中,此时,容器中就有了两个0元素;

      c. 第二、三次循环与第五次循环情况一样:由于线程t1执行的较快,当线程t1执行完add方法后(元素1、2、4已经添加到容器中),线程t2才开始执行contains方法,由于元素1、2、4已经在容器中了,所以线程t2不能将元素1和元素2添加到容器中了;

        d. 第四次循环与第一次循环的情况一样,所以容器中含有两个元素3:

    若向容器中,添加5个不同的元素,则需要将这个复合操作加锁,其改动后的代码如下:

public static void addNumber()
{
    	System.out.println(Thread.currentThread().getName() + ",到达");
    	try {
			cyclicBarrier.await();//使用栅栏,当两个线程都到达后,一起往容器中添加元素
		} catch (Exception e) {
			e.printStackTrace();
		} 
    	System.out.println(Thread.currentThread().getName() + ",开始执行");
//    	synchronized(vector){第一种加锁地点
    		for(int i = 0; i < 5; i++){
        		String str = i + "";
//        		synchronized(vector){第二种加锁地点
        			if(!vector.contains(str))
            		{
            			System.out.println(Thread.currentThread().getName() + ",str = " + str);
            			vector.add(str);
            		}
//        		}
        	}
//    	}
    }

若是对addNumber整个方法加锁,就会出现死锁,其错误代码如下:

1. package test;
2. import java.util.Vector;
3. import java.util.concurrent.CyclicBarrier;
4. public class MyVector2 {
5.     final static  Vector<String> vector = new Vector<String>();
6. 	final static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
7.     public static void main(String[] args) throws InterruptedException {
8.     	testThread();//启动两个线程同时往容器中添加元素
9.     	Thread.sleep(100);//暂停100毫秒,等待两个线程执行完
10.     	vector.sort(null);
11. 		for(int j = 0; j < vector.size(); j++){
12.     		System.out.println("vector.size() = " + vector.size() + " , " + vector.get(j));
13.     	}
14. 		System.out.println(Thread.currentThread().getName() + ",執行完毕。");
15.     }
16.     public static void testThread(){
17.     	new Thread(new Runnable(){
18.     		public void run(){
19.     			addNumber();
20.     		}
21.     	},"t1").start();;
22.     	new Thread(new Runnable(){
23.     		public void run(){
24.     			addNumber();
25.     		}
26.     	},"t2").start();
27.     }
28.     
29.     public static synchronized void addNumber()
30.     {
31.     	System.out.println(Thread.currentThread().getName() + ",到达");
32.     	try {
33. 			cyclicBarrier.await();//使用栅栏,当两个线程都到达后,一起往容器中添加元素
34. 		} catch (Exception e) {
35. 			e.printStackTrace();
36. 		} 
37.     	System.out.println(Thread.currentThread().getName() + ",开始执行");
38.     	for(int i = 0; i < 5; i++){
39.     		String str = i + "";
40.     			if(!vector.contains(str))
41.         		{
42.         			System.out.println(Thread.currentThread().getName() + ",str = " + str);
43.         			vector.add(str);
44.         		}
45.     	}
46.     }
47. }

       出现死锁的原因是:当线程t1开始执行addNumber方式时,要先获取锁,当获取到锁之后并执行到第33行时,线程t1会在此等候第二个线程t2的到来,此时由于线程t1无法释放锁,线程t2一直处于阻塞状态,这时,就出现了死锁情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值