本实验的目的不光是要实现生产者与消费者模式,还要限制生产者与消费者的数量,这样代码的复杂性就提高一些,但好在使用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。