一、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%的是自己程序问题。