关于 Java Concurrency
自从Java诞生之时,Java 就支持并行的概念,比如线程和锁机制。这个教程帮助开发多线程Java程序员能够理解核心的Java并行理念以及如何使用他们。 内容涉及到Java语言中的线程, 重练级以及轻量级同步机制 以及JavaSE 5 中的锁,原子量 并行容器,线程调度 以及线程执行者。 开发人员使用这些知识能够开发好并发线程安全的Java 应用程序。
Java 并行的概念(Java Concurrency Concepts)
概念 |
描述 |
Java 内存模型 |
在JavaSE5 JSR133规范中详细定义了Java内存模型 Java Memory Model(JMM),该模型定义了相关的操作 比如读,写操作,以及在监视器上的同步。 这些操作按 Happens-before的顺序。 这个定义保证了一个线程可以看到另一个线程操作的结果,同时保证了同步的程序, 以及如何定义一个不变的属性 等等。 |
监视器 |
在Java中,任何一个对象都有一个监视器,来排斥共享访问临界区域的代码。这些临界区可以是一个方法 或者是一段代码块,这些临界区域作为同步块。线程只有获取该监视器才能执行同步块的代码。当一个线程到达这块代码是,首先等待来确定是否其他线程已经释放这个监控器。监控器除了排斥共享访问,还能通过Wait 和Notify来协调线程之间的交互。 |
原子属性 |
除了Double 和long类型,其他的简单类型都是原子类型。Double和long 类型的修改在JVM分为两个不封。为了保证更新共享的Double和Long类型,你应该将Double和long 的属性作为Volatile 或者将修改代码放入同步块中。 |
竞争情况 |
当许多线程在一系列的访问共享资源操作中,并且结果跟操作顺便有关系的时候,就发生了竞争情况。 |
数据竞争 |
数据竞争涉及到当许多线程访问不是non-final或者non-volatile 并没有合适的同步机制的属性时,JMM不能保证不同步的访问共享的熟悉。数据竞争导致比个预知的行为。
|
自公布
|
还没有通过构造方法实例化对象之前,把这个对象的引用公布时不安全的。 一种是通过注册一个监听器,当初始化的时候回调来发布引用。 另一种是在构造方法里面启动线程。这两种都会导致其他线程引用部分初始化的对象。 |
Final属性 |
Final属性必须显示的赋值,否则就会有编译错误。一旦赋值,不能被修改。将一个对象引用标记为Final只能保证该引用不会被修改,但该对象可以被修改。比如一个Final ArrayIist不能改变为另一个ArrayList 但你可以添加或者修改这个List的对象。在构造方法之后,一个对象的Final 属性是冻结的,保证了对象被安全的发布。其他线程可以在构造方法时看到该变量,甚至在缺乏同步的机制下。
|
不变对象 |
Final 属性从语义上能够保证创建不变对象。而不变对象可以再没有同步机制下多线程共享和读取。为保证该对象是不变的,必须保证如下: 这个对象被安全的发布,this引用不能在构造方法的时候被发布 所有的属性都是Final的 应用的对象必须保证在构造方法之后不能被修改。 这个对象需要声明为Final 保证子类违法这些原则。 |
Protecting shared data
保护共享的数据
线程安全的程序需要开发人员在需要修改共享的数据时使用合适的锁机制。锁机制建立的
适合JMM的顺序,保证对于其他程序的可视性。
当在同步机制外修改共享的data时,JMM不能保证其一致性。 JVM提供了一些方法来保证其可视性。
Synchronized
每一个对象实体都有一个监视器(来之于Object对象),这个监视器能被再某一线程中锁定。Synchronized关键字来指定在方法或者代码块上持有该对象监视器上的锁定。当某一线程同步修改一属性,后续线程将能看到被该线程修改的数据。
Lock
java.util.concurrent.locks 包提供了Lock的接口,ReentrantLock实现了类似Synchronized关键字的功能。同时还提供了额外的功能,比如不是阻塞的tryLock()方法和释放锁。
同时,在多线程高冲突的情况下,ReentrantLock要比Synchronized效率好。
ReadWriteLock
java.util.concurrent.locks 包提供了一个读写锁的接口,这个接口定义了读和写的一对锁,
一般允许并行的读和排他的写。下面的代码展示了上述功能。
Volatile
Volatile 关键字使其属性对于后续的线程的可见性。
注意:将array标记为Volatile不能保证数组里面元素的Volatile,只能保证数组的引用时
可见的。使用AtomicIntegerArray 来保证整个数组都是可见的。
原子类
Volatile 的缺点是只能保证可见性。不能保证修改结果的可见性。而java.util.concurrent.atomic
包包含了一组支持原子操作的类来弥补Volatile的不足。
ThreadLocal
ThreadLocal存贮了该线程所需要的数据,不需要锁的机制。一般而言,ThreadLocal 存放当前的事务和其他资源等。如下代码,TransactionManager中,ThreadLocal 类型的currentTransaction 存贮了当前事务。
并行容器
合理维护共享数据一致性的核心技术是在访问数据时采取同步机制。这种技术使得所有访问共享数据的方式保证了同步的原则。java.util.concurrent提供了可以并行使用的数据结构。通常而言,使用这些数据结构优于通过Synchronized包装的非同步集合。
同步的 lists and sets
类 |
描述 |
CopyOnWriteArraySet
|
CopyOnWriteArraySet 提供Copy-On-Write的语义 即:每当修改某一数据时在整个容器内容拷贝上修改,然后将该备份同步入容器。
|
CopyOnWriteArrayList
|
类似CopyOnWriteArraySet
|
ConcurrentSkipListSet
|
JSE6提供的并行访问可以排序的Set。 |
并行 maps
java.util.concurrent扩展map接口,提供了名叫ConcurrentMap的并行Map。
如下面所有的操作都是原子性的。
方法 |
描述 |
putIfAbsent(K key, V value) : V
|
如果Key没有在该Map中,将Key Value存入。 否则不做任何处理。 如果没有该Key 返回Null 如果有 返回以前的值。 |
remove(Object key, Object value) : boolean
|
如果Map中包含该key则移出该Value 否则不做任何操作。 |
replace(K key, V value) : V
|
如果Map中有该Key 则用该Value值替换久值。 |
replace(K key, V oldValue, V newValue) : boolean
|
如果Map中有该Key且值为oldValue时,用newValue替换该久值。 |
下面是具体实现类:
类 |
描述 |
ConcurrentHashMap
|
内部的segment实现了并行的读取。 |
ConcurrentSkipListMap
|
JSE6提供的并行访问可排序的Map。 |
Queues
作为生产者于消费者管道的Queues,生产的条目从一头放入,然后从另一头取出,典型的先进先出的顺序。Queues接口在JSE5加入java.util包里,应用于单线程的环境。最主要用于多生产者消费者的情况下。所有的读写操作都在同一Queue上。在Java.util.concurrent包的blockingQueues接口扩张了Queue并处理了Queue可能已经被生产者添加慢的情况,或者消费者已经读取或者取出完,Queue为空的情况。在这些情况下,BlockingQueue提供了阻塞的机制。可以设定阻塞的时间或者阻塞的条件。
下表反应了Queue于BlockingQueue对处理特殊条件下的不同策略。
类 |
策略 |
插入 |
移除 |
检查 |
Queue |
扔出异常 |
Add |
remove |
element |
返回特定的值 |
Offer |
poll |
peek |
|
Blocking Queue |
永远的阻塞 |
Put |
take |
n/a |
在设定的时间阻塞 |
Offer |
poll |
n/a |
下面是具体的实现类。
PriorityQueue |
唯一非并行的Queue。用于单线程 处理排序的集合。 |
ConcurrintlinkedQueue |
没有容量限制的的并行实现,不支持阻塞。 |
ArrayBlockingQueue |
基于数组 有容量限制的 阻塞Queue。 |
LinkedBlockingQueue |
最通用的实现阻塞容量限制的Queue。 |
PriorityBlockingQueue |
相对于先进先出,该Queue的顺序基于Comparator的优先级别,没有容量限制。 |
DelayQueue |
没有容量限制的Queue,有一个延迟值。 只有延迟时间超过时才能被移除。 |
发表评论
最新评论
|
评论