java编程实战第十四章笔记

第十四章 构建自定义的同步工具

一 条件队列

它使得一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件变成真。

正如每个java对象都可以作为一个锁,每个对象同样可以作为一个条件队列,并且Object中的wait,notify和notifyAll方法就构成了内部条件队列的API。

wait:wait是等待的意思,调用wait会自动释放锁,并请求系统挂起当前线程,从而使其他线程能够获得这个锁 
notify:发出通知,解除阻塞条件,JVM会从这个条件队列上等待的多个线程选择一个来唤醒 
notifyAll:发出通知,解除阻塞条件,JVM会唤醒所有在这个条件队列上等待的线程 
条件谓语:线程等待的条件

条件等待中存在一个很重要的三元关系:synchronized,wait和一个条件谓语。 
条件变量由一个锁保护,检查条件谓语时必须先持有锁,调用wait和notifyAll所在方法的对象必须是同一个对象。

notify和notifyAll的区别: 

大多数情况下,应该优先选择notifyAll。假如线程A在条件队列上等待条件谓语PA,线程B在同一个条件队列上等待条件谓语PB,假如线程C将PB变为真,且调用notify,JVM将从众多的等待线程选择其中A来唤醒,但是A看到PA仍然为false,于是继续等待,然而线程B本可以开始执行,却没有被唤醒。

只有满足一下两个条件时,才能用单一的notify而不是notifyAll: 
1. 所有等待线程的类型都相同 
2. 单进单出:在条件变量上的每次通知,最多只能唤醒一个线程来执行 

如果有10个线程在条件队列中等待, 调用notifyAll会唤醒每一个线程, 让它们去竞争锁, 然后它们中的大多数或者全部又回到休眠状态, 这意味着每一个激活单一线程执行的事件, 都会带来大量的上下文切换, 和大量竞争锁的请求

二 显式的Condition对象

内置的条件队列有一些缺陷,每一个内置锁都只能由一个相关联的条件队列。如果想要编写一个带有多个条件谓语的并发对象,可以使用Lock和Condition。

一个Condition和一个单独的Lock相关联, 调用Lock.newCondition()方法, 可以创建一个Condition。每个Lock可以有任意数量的Condition对象. wait, notify, notifyAll在Condition中都有对应的:await, signal, signalAll, 而且一定要使用后者!

使用显式条件变量的有界缓存:

扫描二维码关注公众号,回复: 46412 查看本文章
public class ConditionBoundedBuffer<T> {  
    private static final int BUFFER_SIZE = 2;  
    private final Lock lock = new ReentrantLock();  
    private final Condition notFull = lock.newCondition();  
    private final Condition notEmpty = lock.newCondition();  
    private final T[] items = (T[]) new Object[BUFFER_SIZE];  
    private int tail, head, count;  

    public void put(T x) throws InterruptedException {  
        lock.lock();  
        try {  
            while (count == items.length) {  
                notFull.await();  //只要队列是满的,那么notFull等待
            }  
            items[tail] = x;  
            if (++tail == items.length) {  
                tail = 0;  
            }  
            count++;  
            notEmpty.signal();  //队列不为空,那么唤醒notEmpty
        } finally {  
            lock.unlock();  
        }  
    }  

    public T take() throws InterruptedException {  
        lock.lock();  
        try {  
            while (count == 0) {  
                notEmpty.await();// 只要队列是空的,那么notEmpty等待
            }  
            T x = items[head];  
            items[head] = null;  
            if (++head == items.length) {  
                head = 0;  
            }  
            count--;  
            notFull.signal();  //队列不满,那么唤醒notEmpty
            return x;  
        } finally {  
            lock.unlock();  
        }  
    }  
}  

三 AbstractQueuedSynchronizer(AQS)

AQS是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS构造出来,比如ReentrantLock,Semaphore,CountDownLatch等。

AQS构建的容器中,最基本的就是获取操作和释放操作,对于CountDownLatch,获取意味着等待并直到闭锁到达结束状态,对于FutureTask,获取意味着等待直到任务已经完成。

AQS负责同步容器类中的状态,它管理了一个整数状态信息,可以通过getState,setState以及compareAndSetState来设置和获取。例如ReentrantLock用它来表示线程已经重复获取该锁的次数,Semaphore用它来表示剩余的许可数量,FutureTask用它来表示任务的状态(尚未开始,正在运行,已完成以及以取消)。

下面给出一个使用AQS实现的二元闭锁:

/**
 * 使用AQS实现的二元闭锁
 * @author cream
 *
 */


public class OneShotLatch{
	private final Sync sync = new Sync();
	
	public void signal(){
		sync.releaseShared(0);
	}
	
	public void await() throws InterruptedException{
		sync.acquireSharedInterruptibly(0);
	}
	private class Sync extends AbstractQueuedSynchronizer{
		protected int tryAcquireShared(int ignored){
			//如果闭锁是开的(state==1),那么这个操作成功,否则失败
			return (getState()==1) ? 1 : -1;
		}
		protected boolean tryReleaseShared(int ignored){
			setState(1);//打开闭锁
			return true;//表示其他线程可以获取该闭锁
		}
	}
}
  • public final boolean releaseShared(int arg)

Releases in shared mode. Implemented by unblocking one or more threads if tryReleaseShared(int) returns true.

Parameters:arg - the release argument. This value is conveyed to tryReleaseShared(int) but is otherwise uninterpreted and can represent anything you like.

Returns:the value returned from tryReleaseShared(int)

AQS状态用来表示闭锁状态:关闭(0)或者打开(1)。signal方法会调用releaseShared,接下来又会调用tryReleaseShared,来无条件的把闭锁状态设置为打开。await方法原理类似。

猜你喜欢

转载自blog.csdn.net/llcream/article/details/80018320