`
davidxiaozhi
  • 浏览: 237241 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java多线程学习

阅读更多

关于 Java Concurrency

 
   自从Java诞生之时,Java 就支持并行的概念,比如线程和锁机制。这个教程帮助开发多线程Java程序员能够理解核心的Java并行理念以及如何使用他们。 内容涉及到Java语言中的线程, 重练级以及轻量级同步机制 以及JavaSE 5 中的锁,原子量 并行容器,线程调度 以及线程执行者。 开发人员使用这些知识能够开发好并发线程安全的Java 应用程序。 

Java 并行的概念(Java Concurrency Concepts

 

概念

描述

Java 内存模型

JavaSE5 JSR133规范中详细定义了Java内存模型 Java Memory ModelJMM),该模型定义了相关的操作 比如读,写操作,以及在监视器上的同步。 这些操作按 Happens-before的顺序。

这个定义保证了一个线程可以看到另一个线程操作的结果,同时保证了同步的程序, 以及如何定义一个不变的属性 等等。

监视器

Java中,任何一个对象都有一个监视器,来排斥共享访问临界区域的代码。这些临界区可以是一个方法 或者是一段代码块,这些临界区域作为同步块。线程只有获取该监视器才能执行同步块的代码。当一个线程到达这块代码是,首先等待来确定是否其他线程已经释放这个监控器。监控器除了排斥共享访问,还能通过Wait Notify来协调线程之间的交互。

原子属性

除了Double long类型,其他的简单类型都是原子类型。Doublelong 类型的修改在JVM分为两个不封。为了保证更新共享的DoubleLong类型,你应该将Doublelong 的属性作为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()方法和释放锁。

  

public class Counter {

private final Lock lock = new ReentrantLock();

private int value = 0;

public int increment() {

lock.lock();

try {

return ++value;

}
 finally {

lock.unlock();

}


}


}

 

同时,在多线程高冲突的情况下,ReentrantLock要比Synchronized效率好。

ReadWriteLock

 

java.util.concurrent.locks 包提供了一个读写锁的接口,这个接口定义了读和写的一对锁,

一般允许并行的读和排他的写。下面的代码展示了上述功能。

  

public class Statistic {
private final ReadWriteLock lock = new ReentrantReadWriteLock();

private int value;

public void increment() {

lock.writeLock().lock();

try {

value
++;

}
 finally {

lock.writeLock().unlock();

}


}


public int current() {

lock.readLock().lock();

try {

return value;

}
 finally {

lock.readLock().unlock();

}


}


}

 

Volatile

 

Volatile 关键字使其属性对于后续的线程的可见性。

 

  

public class Processor implements Runnable {

private volatile boolean stop;

public void stopProcessing() {

stop 
= true;

}


public void run() {

while(! stop) {

// .. do processing

}


}


}

 

注意:将array标记为Volatile不能保证数组里面元素的Volatile,只能保证数组的引用时

可见的。使用AtomicIntegerArray 来保证整个数组都是可见的。

 

原子类

 

Volatile 的缺点是只能保证可见性。不能保证修改结果的可见性。而java.util.concurrent.atomic

包包含了一组支持原子操作的类来弥补Volatile的不足。

  

public class Counter {

private AtomicInteger value = new AtomicInteger();

public int next() {

return value.incrementAndGet();

}


}

 

ThreadLocal

     ThreadLocal存贮了该线程所需要的数据,不需要锁的机制。一般而言,ThreadLocal 存放当前的事务和其他资源等。如下代码,TransactionManager中,ThreadLocal  类型的currentTransaction 存贮了当前事务。


public class TransactionManager {

private static final ThreadLocal<Transaction> currentTransaction =

new ThreadLocal<Transaction>() {

@Override

protected Transaction initialValue() {

return new NullTransaction();

}


}
;

public Transaction currentTransaction() {

Transaction current 
= currentTransaction.get();

if(current.isNull()) {

current 
= new TransactionImpl();

currentTransaction.put(current);

}


return current;

}


}



并行容器

     合理维护共享数据一致性的核心技术是在访问数据时采取同步机制。这种技术使得所有访问共享数据的方式保证了同步的原则。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提供了阻塞的机制。可以设定阻塞的时间或者阻塞的条件。

下表反应了QueueBlockingQueue对处理特殊条件下的不同策略。

 

策略

插入

移除

检查

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,有一个延迟值。

只有延迟时间超过时才能被移除。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics