java.util.concurrent中的线程工具
从1.5版本开始,jdk引入了java.util.concurrent包,其中包含了很多用于简化多线程开发的工具,java的并发编程,可以说变得很容易了。
一、线程池。
ExecutorService提供了线程池功能。ExecutorService的实例可以使用new SingleThreadExecutor()或者new FixedThreadPool()来获得。ExecutorService可以用来执行实现了Runnable接口的对象,或者实现了Callable
实例化一个ExecutorService后,就可以调用submit,invokeAll或者invokeAny来执行了。这些方法会返回会返回Future或者List
二、同步机制Lock/Condition,Semaphore,CountDownLatch,CyclicBarrier
java的同步是基于对象锁实现的,所有的对象都天然的带有一个单一锁,JVM会跟踪对象上锁的次数,未加锁时对象上的技术为0,一旦一个对象上锁的计数不为0,则只有已经在这个对象上加锁的线程才能继续获得这个线程上的锁。jdk1.5之前的互斥是通过synchronized关键字实现的,synchronized可以指定加锁的对象,如果没有指定锁定的对象,就锁在同步代码段自己所在的对象实例上。在进入synchronized代码段时对象上锁的技术加1,离开时减1,当同步代码段中有异常抛出时,JVM也会自动释放锁,因而synchronized是个挺好使的东西。
Lock的机制和synchronized是一样的,但是程序员可以手工控制lock和unlock的时机,并且提供了trylock(尝试能够获取锁但不会真的加锁)这样的方法,可控性更强一些。另外,unlock是需要写在finally代码段中的,以防有异常抛出造成锁没有释放。
Lock的一个好处是可以绑定多个Condition。Lock+Condition的组合,类似与synchronized+其加锁的对象上的监视器方法(wait,notify等)。Sun官方给出了一个生产者-消费者的例子,来说明Lock的这种用法。http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/util/concurrent/locks/Condition.html
Semaphore。顾名思义,它确实就是一个信号量……acquire方法获取一个资源,没有资源就会被阻塞,release释放一个资源。与锁的区别……参见各操作系统教科书上的习题集解答。
CountDownLatch是一个倒计时似的计数器,调用countDown()方法会减少计数,计数为0时被其阻塞的线程就被唤醒。CyclicBarrier与CountDownLatch类似,不过不实在计数为0时恢复阻塞的线程的执行,而是在计数到达设定的某个数值时;并且CyclicBarrier是可以用用的,CountDownLatch是一次性的。
三、并发集合类
包括ConcurrentHashMap,ConcurrentLinkedQueue,CopyOnWriteArrayList,BlockingQueue等。这些都是对应的java.utils中的集合类的高性能、线程安全的实现版本。以ConcurrentHashMap来说,其对应的容器版本是HashTable和HashMap/synchronizedMap。HashMap不是线程安全的,而HashTable和synchronizedMap对单个操作是线程安全的,但是多个操作序列如果要保证同步,则必须要加锁,比如put-if-absent操作:如果不包含一个key,那么新增加一个key-value值。HashTable/synchronizedMap同步的代价是比较昂贵的,同步是基于加在这个对象本身上的一个单一锁,这样两个线程甚至没办法并发调用他们get方法,这个同步事实上是读-读也会互斥的。
ConcurrentHashMap实现了putIfAbsenct方法,并且,实现了好得多的并发性。几乎所有的读-读操作可以并发进行,读-写大部分也可以并发进行,甚至写-写操作有时也可以并发。
CopyOnWriteArrayList是一个实现了COW机制的集合类,比较适合于读多写少的情况。
BlockingQueue是一个阻塞队列。为空时获取元素的方法会阻塞,容量满事添加元素的操作会被阻塞。从这个集合类的功能设计可以看出,它的目的用途非常明确……以后用java来写生产者-消费者大作业的同学有福了。
四、原子操作类
Jdk2.5提供了以系列的atomic class来简化同步处理,包括AtomicLong, AtomicInteger, AtomicReference等。在java中,赋值、自增等运算都是无法保证原子性的,原子操作类通过包装了同步操作实现了相关操作的原子化版本。
