线程池的使用

一、java5 多线程
     提到java5的线程,我想大家并不陌生,可能有些兄弟比我用的熟,下面我将应用中遇到的一些问题及相关知识做个小结。
     从JDK 1.5开始,增加了java.util.concurrent包,它的引入大大简化了多线程程序的开发(Doug Lee开发,后来跟同事聊天时得知此人是一大学教授,
     自己开发的代码被收录进jdk1.5,实在是牛啊,呵呵)。
     java.util.concurrent包分成了三个部分,分别是java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.lock。
     内容涵盖了并发集合类、线程池机制、同步互斥机制、线程安全的变量更新工具类、锁等常用工具。下面就本次开发中用到的以及相对常用的知识列举下。
     
     1.线程池
         线程池的具体概念及优点大家可以google下。
         创建线程最常用的类就是Excutors,通过这个类能够获得多种线程池的实例,例如可以调用newSingleThreadExecutor()获得单线程的ExecutorService,
     调用newFixedThreadPool()获得固定大小线程池的ExecutorService。拿到ExecutorService可以做的事情就比较多了,最简单的是用它来执行Runnable对象,
     也可以执行一些实现了Callable<T>的对象。用Thread的start()方法没有返回值,如果该线程执行的方法有返回值那用ExecutorService就再好不过了,
     可以选择submit()、invokeAll()或者invokeAny(),根据具体情况选择合适的方法即可。
         我在开发中用的是ScheduledExecutorService,利用它可以创建能够周期性的执行任务的线程池。在java5之前,如果要实现周期性的执行任务可以有两种选择。
     一是利用Timer和TimerTask;二是在线程类中加入Thread.sleep()方法。关于ScheduledExecutorService的具体用法,api中有具体描述,有兴趣的兄弟可以看下。
    
     2.线程锁
         提到线程锁的实现,java5之前我们用的是synchronized。java5之后,我们多了一种选择,那就是java.util.concurrent.locks。通过Lock能够实现更灵活的
     锁定机制,它还提供了很多synchronized所没有的功能,例如尝试获得锁(tryLock())。
         使用Lock时需要自己获得锁并在使用后手动释放,这一点与synchronized有所不同,所以通常Lock的使用方式是这样的:
         Java代码
         Lock l = ...;    
         l.lock();   
         try {   
             // 执行操作   
         } finally {   
             l.unlock();   
         }  
java.util.concurrent.locks中提供了几个Lock接口的实现类,比较常用的应该是ReentrantLock。用法请参见api。
         这里还要说一个东东,那就是Conditon。java5中的Lock代替了synchronized,Conditon则代替了Object对象上的wait()、notify()和notifyAll()方法
(Condition中提供了await()、signal()和signalAll()方法),当满足运行条件前挂起线程。Conditon是与Lock结合使用的。示例代码如下:
Lock lock = new ReentrantLock();   
Condition notFull   = lock.newCondition();  
通过Lock.newCondition()方法能够创建与Lock绑定的Condition实例。
另外ReentrantLock中实现了读写锁,有兴趣的可以看下。
     3.并发集合类
         集合类是大家编程时经常要使用的东西,ArrayList、HashMap什么的,java.util包中的集合类有的是线程安全的,有的则不是。java.util.concurrent包
     中提供了几个并发结合类,例如ConcurrentHashMap、ConcurrentLinkedQueue和 CopyOnWriteArrayList等等,根据不同的使用场景,开发者可以用它们替换
     java.util包中的相应集合类。关于ConcurrentHashMap的实现,大家可以去翻源代码,结合中文api以及网上的一些文章,我想大家会从中体会到其中的妙处(线程

     安全性、性能方面)。

 

     4.Atomic
         对变量的读写操作都是原子操作(除了long或者double的变量),但像数值类型的++ --操作不是原子操作,像i++中包含了获得i的原始值、加1、写回i、
     返回原始值,在进行类似i++这样的操作时如果不进行同步问题就大了。好在java.util.concurrent.atomic为我们提供了很多工具类,可以以原子方式更新变量。
     以AtomicInteger为例,提供了代替++ --的getAndIncrement()、incrementAndGet()、getAndDecrement()和decrementAndGet()方法,还有加减给定值的方法、

     当前值等于预期值时更新的compareAndSet()方法。

 

         多线程其他的知识就不赘述了,实际应用中可以翻查api,为自己的代码量身定做,呵呵。java5的多线程看似很傻瓜,可要想真的用到位,还是需要下点功夫滴。另外,
     java5的多线程对于有些异常直接catch掉了,这给我们调试代码带来了麻烦,这就要求我们写代码时必须要小心。
二、枚举实现单件模式
         学过C或C++的兄弟对枚举并不陌生,java5以后也引入了枚举类型。枚举的基本概念及应用google一下便知。这里主要阐述应用枚举类型实现单件模式。单件模式(新来的兄弟
     可能不是十分清楚)保证一个类仅有一个实例,并提供一个该实例全局的访问点(GOF《设计模式》中这样描述)。在多线程编程中,为了保证类对象的唯一,单件模式更为常见。
     据我了解,单件模式的实现有3种,这里只探讨枚举类型实现单件模式。枚举类型的构造器本身是私有的,这给构建单件模式带来了方便。具体实现如下:
     public enum SDAMonitorCons
     {
/**
      * 枚举对象
      */
     MONITORCONSINSTANCE;
     /*
      * 需要监控的机房Map,Map的key为机房编号,value为map, value中存储sda机房编号、机房类型、监控时间、机房采集点、机房名称
      */
private Map<String, Map<String, String>> stationMap = new ConcurrentHashMap<String, Map<String, String>>();
/**
* 获取机房信息
*
* @return stationMap
*/
public Map<String, Map<String, String>> getStationMap()
{
return stationMap;
}
/**
* 设置监控机房信息,刷新需要监控的机房信息
*
* @param station
*             需要监控的机房信息
*/
public void setStationMap(Map<String, Map<String, String>> station)
{
stationMap.putAll(station);
}
     }
     我们可以这样调用SDAMonitorCons中的方法:SDAMonitorCons.MONITORCONSINSTANCE.getStationMap();
三、Map或者Collection的删除操作
         迭代Map或Collection的时候调用remove删除其中元素,会产生java.util.ConcurrentModificationException异常。原因:Iterator是工作在一个独立的线程中,
     并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表(我觉得就是原对象的clone),当原来的对象数量发生变化时,这个索引表
     的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException
     异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前
     迭代对象的同时维护索引的一致性。有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。
     这也就是为什么在 javadoc 里面出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException
     should be used only to detect bugs.
     给出一段错误代码:
     /*
      *迭代stationMap(HashMap对象)
      */
     for(Map.Entry<String, Map<String, String>> entry:stationMap)
     {
   //do something(比如取map中的值等操作)
         stationMap.remove(stationId);
     }
     这里应用的是for-each循环,其实现是基于Iterator的。上述代码会出现java.util.ConcurrentModificationException异常。
     正确代码:
     Iterator<Map.Entry<String, Map<String, String>>> it = stationMap.entrySet().iterator();
     while (it.hasNext())
    {
//do something(比如取map中的值等操作)
//这一行能保证对象的一致
it.remove();
stationMap.remove(stationId);
    }
    如果这个问题出现在普通的java代码中,我们会很容易发现并解决。但偏偏我将其应用在java5的多线程代码中,而java5的多线程catch掉这个异常,调试起来费了很大周折,
    被蹂躏的一沓,呵呵。另外,我在应用中发现,n次执行结果中第一次始终是正确的,这个问题我还没想明白。
四、不要轻易怀疑jdk
    开发过程中,遇到棘手的问题时,尤其是在应用一些java新特性时,不要轻易怀疑jdk有bug。这个时候你应该冷静的意识到99.99999%的是自己程序问题。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值