Java并发控制机制
转载自:http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/
在一般性开发中,笔者经常看到很多同学在对待java并发开发模型中只会使用一些基础的方法。比如volatile,synchronized。像Lock和atomic这类高级并发包很多人并不经常使用。我想大部分原因都是来之于对原理的不属性导致的。在繁忙的开发工作中,又有谁会很准确的把握和使用正确的并发模型呢?
所以最近基于这个思想,本人打算把并发控制机制这部分整理成一篇文章。既是对自己掌握知识的一个回忆,也是希望这篇讲到的类容能帮助到大部分开发者。
并行程序开发不可避免地要涉及多线程、多任务的协作和数据共享等问题。在JDK中,提供了多种途径实现多线程间的并发控制。比如常用的:内部锁、重入锁、读写锁和信号量。
Java内存模型
在java中,每一个线程有一块工作内存区,其中存放着被所有线程共享的主内存中的变量的值的拷贝。当线程执行时,它在自己的工作内存中操作这些变量。
为了存取一个共享的变量,一个线程通常先获取锁定并且清除它的工作内存区,这保证该共享变量从所有线程的共享内存区正确地装入到线程的工作内存区,当线程解锁时保证该工作内存区中变量的值协会到共享内存中。
当一个线程使用某一个变量时,不论程序是否正确地使用线程同步操作,它获取的值一定是由它本身或者其他线程存储到变量中的值。例如,如果两个线程把不同的值或者对象引用存储到同一个共享变量中,那么该变量的值要么是这个线程的,要么是那个线程的,共享变量的值不会是由两个线程的引用值组合而成。
一个变量时Java程序可以存取的一个地址,它不仅包括基本类型变量、引用类型变量,而且还包括数组类型变量。保存在主内存区的变量可以被所有线程共享,但是一个线程存取另一个线程的参数或者局部变量时不可能的,所以开发人员不必担心局部变量的线程安全问题。至于内存模型中线程工作内存与主内存的交互请关注http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html, 这里就不再做过多介绍。
volatile变量–多线程间可见
由于每个线程都有自己的工作内存区,因此当一个线程改变自己的工作内存中的数据时,对其他线程来说,可能是不可见的。为此,可以使用volatile关键字破事所有线程军读写内存中的变量,从而使得volatile变量在多线程间可见。
声明为volatile的变量可以做到如下保证:
1、其他线程对变量的修改,可以及时反应在当前线程中;
2、确保当前线程对volatile变量的修改,能及时写回到共享内存中,并被其他线程所见;
3、使用volatile声明的变量,编译器会保证其有序性。
同步关键字synchronized
同步关键字synchronized是Java语言中最为常用的同步方法之一。在JDK早期版本中,synchronized的性能并不是太好,值适合于锁竞争不是特别激烈的场合。在JDK6中,synchronized和非公平
锁的差距已经缩小。更为重要的是,synchronized更为简洁明了,代码可读性和维护性比较好。
锁定一个对象的方法:
1 |
public synchronized void method(){} |
public void method(){
synchronized(this){
// do something …
}
}
1 |
|
public void method(Object o){
// before
synchronized(o){
// do something ...
}
// after
}
1 |
|
public synchronized static void method(){}
1 |
|
synchronized(obj){
while(<?>){
obj.wait();
// 收到通知后,继续执行。
}
}
1 |
|
public class BlockQueue{
private List list = new ArrayList();
public synchronized Object pop() throws InterruptedException{
while (list.size()==0){
this.wait();
}
if (list.size()>0){
return list.remove(0);
} else{
return null;
}
}
public synchronized Object put(Object obj){
list.add(obj);
this.notify();
}
}
1 |
|
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
// lock.lockInterruptibly();可以响应中断事件
try {
//操作
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
}
1 |
|
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
public Object handleRead() throws InterruptedException {
try {
readLock.lock();
Thread.sleep(1000);
return value;
}finally{
readLock.unlock();
}
}
public Object handleRead() throws InterruptedException {
try {
writeLock.lock();
Thread.sleep(1000);
return value;
}finally{
writeLock.unlock();
}
}
1 |
|
public class ArrayBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable {
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition(); // 生成与Lock绑定的Condition
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal(); // 通知
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) // 如果队列为空
notEmpty.await(); // 则消费者队列要等待一个非空的信号
return extract();
} finally {
lock.unlock();
}
}
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal(); // 通知put() 线程队列已有空闲空间
return x;
}
// other code
}
1 |
|
public class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
// 申请一个许可
// 同时只能有100个线程进入取得可用项,
// 超过100个则需要等待
return getNextAvailableItem();
}
public void putItem(Object x) {
// 将给定项放回池内,标记为未被使用
if (markAsUnused(x)) {
available.release();
// 新增了一个可用项,释放一个许可,请求资源的线程被激活一个
}
}
// 仅作示例参考,非真实数据
protected Object[] items = new Object[MAX_AVAILABLE]; // 用于对象池复用对象
protected boolean[] used = new boolean[MAX_AVAILABLE]; // 标记作用
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null;
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else {
return false;
}
}
}
return false;
}
}
1 |
|
public class TestNum {
// 通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal seqNum = new ThreadLocal() {
public Integer initialValue() {
return 0;
}
};
// 获取下一个序列值
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String[] args) {
TestNum sn = new TestNum();
//3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private TestNum sn;
public TestClient(TestNum sn) {
this.sn = sn;
}
public void run() {
for (int i = 0; i < 3; i++) {
// 每个线程打出3个序列值
System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["
+ sn.getNextNum() + "]");
}
}
}
}
1 |
|
thread[Thread-0] –> sn[1]
thread[Thread-1] –> sn[1]
thread[Thread-2] –> sn[1]
thread[Thread-1] –> sn[2]
thread[Thread-0] –> sn[2]
thread[Thread-1] –> sn[3]
thread[Thread-2] –> sn[2]
thread[Thread-0] –> sn[3]
thread[Thread-2] –> sn[3]
1 |
|
public synchronized void syncMehod(){
beforeMethod();
mutexMethod();
afterMethod();
}
1 |
|
public void syncMehod(){
beforeMethod();
synchronized(this){
mutexMethod();
}
afterMethod();
}
1 |
|
public class LinkedBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable {
/* Lock held by take, poll, etc /
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 不能有两个线程同时读取数据
try {
while (count.get() == 0) { // 如果当前没有可用数据,一直等待put()的通知
notEmpty.await();
}
x = dequeue(); // 从头部移除一项
c = count.getAndDecrement(); // size减1
if (c > 1)
notEmpty.signal(); // 通知其他take()操作
} finally {
takeLock.unlock(); // 释放锁
}
if (c == capacity)
signalNotFull(); // 通知put()操作,已有空余空间
return x;
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 不能有两个线程同时put数据
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) { // 队列满了 则等待
notFull.await();
}
enqueue(node); // 加入队列
c = count.getAndIncrement();// size加1
if (c + 1 < capacity)
notFull.signal(); // 如果有足够空间,通知其他线程
} finally {
putLock.unlock();// 释放锁
}
if (c == 0)
signalNotEmpty();// 插入成功后,通知take()操作读取数据
}
// other code
}
1 |
|
public void syncMehod(){
synchronized(lock){
method1();
}
synchronized(lock){
method2();
}
}
1 |
|
public void syncMehod(){
synchronized(lock){
method1();
method2();
}
}
1 |
|
public class TestAtomic {
private static final int MAX_THREADS = 3;
private static final int TASK_COUNT = 3;
private static final int TARGET_COUNT = 100 * 10000;
private AtomicInteger acount = new AtomicInteger(0);
private int count = 0;
synchronized int inc() {
return ++count;
}
synchronized int getCount() {
return count;
}
public class SyncThread implements Runnable {
String name;
long startTime;
TestAtomic out;
public SyncThread(TestAtomic o, long startTime) {
this.out = o;
this.startTime = startTime;
}
@Override
public void run() {
int v = out.inc();
while (v < TARGET_COUNT) {
v = out.inc();
}
long endTime = System.currentTimeMillis();
System.out.println("SyncThread spend:" + (endTime - startTime) + "ms" + ", v=" + v);
}
}
public class AtomicThread implements Runnable {
String name;
long startTime;
public AtomicThread(long startTime) {
this.startTime = startTime;
}
@Override
public void run() {
int v = acount.incrementAndGet();
while (v < TARGET_COUNT) {
v = acount.incrementAndGet();
}
long endTime = System.currentTimeMillis();
System.out.println("AtomicThread spend:" + (endTime - startTime) + "ms" + ", v=" + v);
}
}
@Test
public void testSync() throws InterruptedException {
ExecutorService exe = Executors.newFixedThreadPool(MAX_THREADS);
long startTime = System.currentTimeMillis();
SyncThread sync = new SyncThread(this, startTime);
for (int i = 0; i < TASK_COUNT; i++) {
exe.submit(sync);
}
Thread.sleep(10000);
}
@Test
public void testAtomic() throws InterruptedException {
ExecutorService exe = Executors.newFixedThreadPool(MAX_THREADS);
long startTime = System.currentTimeMillis();
AtomicThread atomic = new AtomicThread(startTime);
for (int i = 0; i < TASK_COUNT; i++) {
exe.submit(atomic);
}
Thread.sleep(10000);
}
}
1 |
|
testSync():
SyncThread spend:201ms, v=1000002
SyncThread spend:201ms, v=1000000
SyncThread spend:201ms, v=1000001
testAtomic():
AtomicThread spend:43ms, v=1000000
AtomicThread spend:44ms, v=1000001
AtomicThread spend:46ms, v=1000002
```
相信这样的测试结果将内部锁和非阻塞同步算法的性能差异体现的非常明显。因此笔者更推荐直接视同atomic下的这个原子类。
结束语
终于把想表达的这些东西整理完成了,其实还有一些想CountDownLatch这样的类没有讲到。不过上面的所讲到的绝对是并发编程中的核心。也许有些读者朋友能在网上看到很多这样的知识点,但是个人还是觉得知识只有在对比的基础上才能找到它合适的使用场景。因此,这也是笔者整理这篇文章的原因,也希望这篇文章能帮到更多的同学。

浙公网安备 33010602011771号