本实验的目的不光是要实现生产者与消费者模式,还要限制生产者与消费者的数量,这样代码的复杂性就提高一些,但好在使用Semaphore类实现这个功能还是比较简单的。

创建实验用的项目repastTest,类RepastService.java代码如下:

package com.yc.semephore_6;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class RepastService {
	private Semaphore setsemaphore = new Semaphore(10); //厨师(生产者)
	private Semaphore getsemaphore = new Semaphore(20);//顾客(消费者)
	private Object[] produces = new Object[4]; //允许最多有4个盘子
	
	private ReentrantLock lock = new ReentrantLock();
	private Condition setCondition = lock.newCondition(); //控制厨师(生产者)的condition
	private Condition getCondition = lock.newCondition(); //控制顾客(消费者)的condition
	
	//判断盘子produces是否为空(一份食物都没装)
	private boolean isEmpty(){
		boolean isEmpty = true;
		for(int i = 0; i < produces.length; i ++){
			if(produces[i] != null){
				isEmpty = false;
				break;
			}
		}
		/*if(isEmpty){
			return true;
		}else{
			return false;
		}*/
		
		return isEmpty;
	}
	//判断盘子是否装满
	private boolean isFull(){
		boolean isFull = true;
		for(int i = 0; i < produces.length; i ++){
			if(produces[i] == null){
				isFull = false;
				break;
			}
		}
		/*if(isFull){
			return true;
		}else{
			return false;
		}*/
		return isFull;
	}
	
	//生产
	public void set(){
		try {
			//允许最多 10/1= 10 个厨师生产
			setsemaphore.acquire();
			//线程获得锁
			lock.lock();
			//如果盘子用完了,那厨师就去休息
			while(isFull()){
				setCondition.await();
			}
			//生产(每个厨师只生产一份)
			for(int i = 0; i < produces.length; i ++){
				if(produces[i] == null){
					produces[i] = "土豆丝炒肉" + i;
					System.out.println( Thread.currentThread().getName() + "\t生产了\t" + produces[i]);
					break;
				}
			}
			//给所有顾客一个signal,告诉它菜好了
			getCondition.signalAll();
			//释放锁
			lock.unlock();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally{
			setsemaphore.release();
		}
	}
	
	//消费
	public void get(){
		try {
			//允许最多有20 个消费者
			getsemaphore.acquire();
			//消费者得到锁
			lock.lock();
			//如果盘子都是空的,那消费者就慢慢等吧
			while(isEmpty()){
				getCondition.await();
			}
			//消费
			for(int i = 0; i < produces.length; i ++){
				if(produces[i] != null){
					System.out.println( Thread.currentThread().getName() + "\t消费了\t" + produces[i]);
					produces[i] = null; //进食(吃完了,盘子里的食物就为空)
					break;
				}
			}
			//消费者吃完了,然后说:我吃完了,你们接着生产啊
			setCondition.signalAll();
			//释放锁
			lock.unlock();
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally{
			getsemaphore.release();
		}
	}
}



下面是两个线程类生产者(厨师)和消费者(顾客):

package com.yc.semephore_6;

public class SetRun extends Thread{
	private RepastService service;
	
	public SetRun(RepastService service){
		this.service = service;
	}
	@Override
	public void run() {
		super.run();
		service.set();
	}
	
}
package com.yc.semephore_6;

public class GetRun extends Thread{
	private RepastService service;
	
	public GetRun(RepastService service){
		this.service = service;
	}
	@Override
	public void run() {
		super.run();
		service.get();
	}
	
}



运行类RepastTest.java代码如下:


package com.yc.semephore_6;

public class RepastTest {
	public static void main(String[] args) throws InterruptedException {
		RepastService service = new RepastService();
		
		SetRun[] setRunP = new SetRun[15]; //现在有15个厨师
		GetRun[] getRunC = new GetRun[40]; //并且有40个顾客
		
		for(int i = 0; i < getRunC.length; i ++){
			getRunC[i] = new GetRun(service);
			if(i < setRunP.length){
				setRunP[i] = new SetRun(service);
			}
		}
		
		//给3秒的时间让上面的厨师和顾客到位
		Thread.sleep(3 * 1000);
		
		for(int i = 0; i < getRunC.length; i ++){
			if(i < setRunP.length){
				setRunP[i].start();
			}
			getRunC[i].start();
		}
	}
}




测试的某一次的输出结果为:


Thread-1	生产了	土豆丝炒肉0
Thread-0	消费了	土豆丝炒肉0
Thread-5	生产了	土豆丝炒肉0
Thread-4	消费了	土豆丝炒肉0
Thread-9	生产了	土豆丝炒肉0
Thread-2	消费了	土豆丝炒肉0
Thread-7	生产了	土豆丝炒肉0
Thread-3	生产了	土豆丝炒肉1
Thread-13	生产了	土豆丝炒肉2
Thread-12	消费了	土豆丝炒肉0
Thread-17	生产了	土豆丝炒肉0
Thread-11	生产了	土豆丝炒肉3
Thread-20	消费了	土豆丝炒肉0
Thread-25	生产了	土豆丝炒肉0
Thread-24	消费了	土豆丝炒肉0
Thread-29	生产了	土豆丝炒肉0
Thread-28	消费了	土豆丝炒肉0
Thread-32	消费了	土豆丝炒肉1
Thread-33	消费了	土豆丝炒肉2
Thread-8	消费了	土豆丝炒肉3
Thread-27	生产了	土豆丝炒肉0
Thread-16	消费了	土豆丝炒肉0
Thread-21	生产了	土豆丝炒肉0
Thread-10	消费了	土豆丝炒肉0
Thread-15	生产了	土豆丝炒肉0
Thread-19	生产了	土豆丝炒肉1
Thread-18	消费了	土豆丝炒肉0
Thread-23	生产了	土豆丝炒肉0
Thread-22	消费了	土豆丝炒肉0
Thread-6	消费了	土豆丝炒肉1



一共三十个线程((厨师=10) + (顾客=20) = 30),注意这里的 “土豆丝炒肉0” 中的 0 指的是第0个盘子,意思是某个厨师(线程)生产的 土豆丝炒肉 放在了第 0 个盘子里。

下面我来解释下RepastService.java中的set()和get()方法中的相关代码:

set():

//允许最多 10/1= 10 个厨师生产
setsemaphore.acquire();


由前面的private Semaphore setsemaphore = new Semaphore(10);可知该对象可由10个线程同时访问。


//线程获得锁
lock.lock();


说明10个线程中谁先得到锁谁先锁定该对象并执行接下来的代码。


//如果盘子用完了,那厨师就去休息
while(isFull()){
	setCondition.await();
}


setCondition指的是setSemaphore的ConditionObject(ConditionObject implements Condition,虽然CondtionObject是AbstractQueuedSynchronized(AQS)的内部类),也可以说是线程x,await()方法JDK1.7源码如下:


public final void await() throws InterruptedException {
if (Thread.interrupted())
 throw new InterruptedException();
 Node node = addConditionWaiter(); //将当前线程包装下后,
                                   //添加到Condition自己维护的一个链表中。
int savedState = fullyRelease(node);//释放当前线程占有的锁,从demo中看到,
                                       //调用await前,当前线程是占有锁的

int interruptMode = 0;
 while (!isOnSyncQueue(node)) {//释放完毕后,遍历AQS的队列,看当前节点是否在队列中,
                           //不在 说明它还没有竞争锁的资格,所以继续将自己沉睡。
                             //直到它被加入到队列中,聪明的你可能猜到了,
                            //没有错,在singal的时候加入不就可以了?
 LockSupport.park(this);
 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
 break;
 }
//被唤醒后,重新开始正式竞争锁,同样,如果竞争不到还是会将自己沉睡,等待唤醒重新开始竞争。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
 interruptMode = REINTERRUPT;
 if (node.nextWaiter != null)
 unlinkCancelledWaiters();
 if (interruptMode != 0)
 reportInterruptAfterWait(interruptMode);
 }

上面的await()方法的作用就是将该线程(setSemaphore.acquire()得到许可的线程)加入到ConditionObject内部类中的等待Signal信号的队列中,并且让其等待其他线程给它一个Signal信号让他重新加入到AQS本身的等待资源(CPU)的队列中。




//生产(每个厨师只生产一份)
for(int i = 0; i < produces.length; i ++){
	if(produces[i] == null){
		produces[i] = "土豆丝炒肉" + i;
		System.out.println( Thread.currentThread().getName() + "\t生产了\t" + produces[i]);
		break;
	}
}


for循环的作用就是生产了一个产品(土豆丝炒肉),并放入到第 i 个盘子里(从第一个盘子遍历,哪个盘子是空的就放入哪个盘子)。




//给所有顾客一个signal告诉它菜好了
getCondition.signalAll();


getCondition可以说是线程y,它的作用就是给所有的在ConditionObject中的等待Signal信号的线程,就上面的代码而言线程x生产完第一份产品后(for循环),就把这个Signal传给ConditionObject中的等待Signal信号的线程,也就是顾客。紧接着顾客(getCondition)得到这个Signal后把自己加入到AQS等待资源(CPU)的队列中。

下面是SignalAll()的JDK1.7源码:

/**
         * Moves all threads from the wait queue for this condition to
         * the wait queue for the owning lock.
         *(将所有的Condition中的等待Signal信号线程移入到AQS等待资源的队列中)
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;	//firstWaiter指的是被封装在Condition中的等待Signal信号的线程的 线程头,因为它是以链表的形式关联的
            if (first != null)
                doSignalAll(first);
        }


/**
         * Removes and transfers all nodes.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }





//释放锁
lock.unlock();

释放锁线程要做的事:AQS会在资源被释放后,依次唤醒AQS队列中从前到后的所有节点,使他们对应的线程恢复执行。直到队列为空。



流程大概为:

1. 线程x调用reentrantLock.lock时,线程被加入到AQS的等待队列中。

2. 线程x调用await方法被调用时,该线程从AQS中移除,对应操作是锁的释放。

3. 接着马上被加入到Condition的等待队列中,以为着该线程需要signal信号。

4. 线程y,因为线程x释放锁的关系,被唤醒,并判断可以获取锁,于是线程y获取锁,并被加入到AQS的等待队列中。

5.  线程y调用signal方法,这个时候Condition的等待队列中只有线程x一个节点,于是它被取出来,并被加入到AQS的等待队列中。  注意,这个时候,线程x 并没有被唤醒。

6. signal方法执行完毕,线程y调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程x,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒,于是线程x回复执行。

7. 直到释放所整个过程执行完毕。

总之要记住5件事:①await()把自己加到Condition的等待Signal信号队列中。

②signalAll()把自己加到AQS等待资源的队列中。

③lock()获得锁

④unlock()释放锁:AQS从头到尾唤醒AQS中的等待资源的线程,直到尾。

⑤这种实现就是Condition中的等待Signal线程和AQS中等待资源的线程相互remove和transfer。