【转】
关于 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不能保证不同步的访问共享的熟悉。数据竞争导致比个预知的行为。 ? |
自公布 ? |
还没有通过构造方法实例化对象之前,把这个对象的引用公布时不安全的。 一种是通过注册一个xxx,当初始化的时候回调来发布引用。 另一种是在构造方法里面启动线程。这两种都会导致其他线程引用部分初始化的对象。 |
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()方法和释放锁。
??
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 包提供了一个读写锁的接口,这个接口定义了读和写的一对锁,
一般允许并行的读和排他的写。下面的代码展示了上述功能。
??
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 关键字使其属性对于后续的线程的可见性。
?
??
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的不足。
??
private?AtomicInteger?value?=?new?AtomicInteger();
public?int?next()?{
return?value.incrementAndGet();
}
}
?
ThreadLocal
???? ThreadLocal存贮了该线程所需要的数据,不需要锁的机制。一般而言,ThreadLocal 存放当前的事务和其他资源等。如下代码,TransactionManager中,ThreadLocal ?类型的currentTransaction 存贮了当前事务。
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
?