Java高并发学习(七)

Java高并发学习(7)


程序中的幽灵:隐蔽的错误

  作为一名软件开发人员,修复BUG应该是基本的日常工作之一。作为java程序员,也许你经常会被抛出的一大堆异常堆栈所困扰。因为这可能预示着你又有工作可做了。但是我在这里想说的是,如果程序出错,你看到了异常堆栈,那你应该感到格外的高兴。最可怕的情况是:系统没有任何异常表现,没有日志,也没有堆栈。但是却给出了个错误的执行结果,这种情况才让你抓狂。不幸的是,错误的使用并行,会非常容易产生这类问题。接下来列举几个在多线程程序中容易忽略的“幽灵”。

 

1. 并发下的ArrayList

     我们都知道,ArrayList是一个线程不安全的容器。如果在多线程中使用ArrayList,可能会导致程序出错。那究可能引起哪些问题呢?试看下面的代码:

import java.util.ArrayList;
public class fist{
    static ArrayList<Integer> al = new ArrayList<Integer>(10); 
    public static class MyThread extends Thread{
        @Override
        public void run(){
            for(int i = 0;i<100000;i++){
                al.add(i);
            }
        }
    }
    
    public static void main(String args[]) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(al.size());
    }
}


输出结果:

   上述代码中,t1和t2两个线程同时向一个ArrayList容器添加元素。他们各自添加100000个元素,因此我们期望最后可以有200000个元素在ArrayList里。但如果你执行这段代码,你可能会得到3种结果。

        (1) .程序正常结束,ArrayList的最终大小确实是200000。这说明并行程序即使有问题,但是一次的运行并不能将其显示出来。

        (2) .程序抛出异常,并且ArrayList的大小小于200000。上面的输出结果就是这个情况。

   程序抛出异常是因为,ArrayList在扩容的过程中,内部一致性被破坏,但由于没有锁的保护,另外的一个线程访问到了不一致多的内部状态,导致出现越界问题。ArrayList的大小小于200000,是因为,多线程的访问冲突,使得保存容器大小的变量被多线程不正常的访问,同时两个线程也同时对ArrayList中的同一个位置进行赋值导致的。

         那么如何解决这个问题呢?其实很简单,使用线程安全的vector代替ArrayList就可以了。(小结:ArrayList是线程不安全容器,vector是线程安全容器)

 

 

2. 并发下诡异的HashMap。

        Hashmap同样不是线程安全的。当你使用多线程访问Hashmap时,也可能会遇到意想不到的错误。不过和ArrayList不同,Hashmap的问题似乎更加诡异。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
 
public class fist{
    static Map<String, String> map = new HashMap<String,String>();
    public static class MyThread extends Thread{
        int start = 0;
        public MyThread(int start){
            this.start = start;
        }
        @Override
        public void run(){
            for(int i = start;i<10000;i+=2){
                map.put(Integer.toString(i), Integer.toBinaryString(i));
            }
        }
    }
    
    public static void main(String args[]) throws InterruptedException {
        MyThread t1 = new MyThread(0);
        MyThread t2 = new MyThread(1);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(map.size());
    }
}


   上述代码使用t1和t2两个线程同时对HashMap进行put()操作。如果一切正常,我们期待的map.size()就是100000。但实际上,你可能会得到以下三种情况。

        (1) .程序正常结束,结果也是符合预期的。HashMap的大小为100000.

        (2) .程序正常结束,但结果不符合预期,而是一个小于100000的数字。

        (3) .程序永远无法结束。

   对于前两种可能,和ArrayList的情况非常相似,因此,不必解释。而对于第三种情况,如果是第一次看到,我想大家一定会觉得惊讶,因为看似非常正常的程序,这么可能结束不了呢?

   这属于HashMap内部构造问题,会造成访问它的两个线程死锁,这个死循环一旦发生,着实可以让你郁闷一把。但这个问题在JDK8中已经不存在了。由于JDK8对HashMap的内部实现做了大规模的调整,因此规避了这个问题。但,即使这样,贸然在多线程环境下使用HashMap依然会导致内部数据不一致。最简单的解决方案是使用ConcurrenHashMap代替HashMap。

 

 

3.  总结

   在多线程环境中,若要使用java提供的容器,先查看是否为线程安全容器,这很重要
--------------------- 
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值