A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© pengbin 中级黑马   /  2015-7-24 12:45  /  3554 人查看  /  16 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

3黑马币
下面这个生产消费的程序为什么会产生安全问题?

package test;
import java.util.concurrent.locks.*;

class test
{
public static void main(String[] arsg)
{
  Res r = new Res();
  produce pro = new produce(r);
  consumer con = new consumer(r);
  
  Thread t1 = new Thread(pro);
  Thread t2 = new Thread(pro);
  Thread t3 = new Thread(con);
  Thread t4 = new Thread(con);
  
  t1.start();
  t2.start();
  t3.start();
  t4.start();
}
}
class Res
{
private boolean flag =  false;
public static int count = 0;
private Lock lock = new ReentrantLock();
private Condition con_set = lock.newCondition();
private Condition con_out = lock.newCondition();

public void set()
{
  lock.lock();
  try
  {
   while(flag)
    con_set.await();
   System.out.println("生产者*******"+count++);
   flag = true;
   con_out.signal();
  }
  catch(Exception e)
  {
   System.out.println("*****************************");
  }
  finally
  {
   lock.unlock();
  }
}

public void out()
{
  lock.lock();
  try
  {
   while(!flag)
    con_out.await();
   System.out.println("*******消费者*******"+count++);
   flag = false;
   con_set.signal();
  }
  catch(Exception e)
  {
   System.out.println("*****************************");
  }
  finally
  {
   lock.unlock();
  }
}
}
class produce implements Runnable
{
Res r;
produce(Res r)
{
  this.r=r;
}
public void run()
{
  
  
   while(Res.count<=100000)
   {
    r.set();
   }
  
}
}
class consumer implements Runnable
{
Res r;
consumer(Res r)
{
  this.r=r;
}
public void run()
{
  
   while(Res.count<=100000)
   {
    r.out();
   }
  
   
  
}
}

QQ图片20150724124558.png (5.33 KB, 下载次数: 15)

QQ图片20150724124558.png

最佳答案

查看完整内容

楼主经过我仔细研究 没问题真的count变量改一下别2个都count++ , 关于上面哥们说的: 怀疑是这样的,某时刻消费者1抢到锁对象,进入同步代码快,判断条件不符,await()了,就放弃了锁对象,但是它仍处于同步代码区。然后消费者2抢到了锁对象,判断条件也不符,也await()了,放弃了锁对象,它也仍处于同步代码区。下一次恰好生产者1抢到锁对象,判断条件符合,执行完后signal(),这个时候同时唤醒了两个消费者,由于他们都已 ...

16 个回复

倒序浏览
楼主经过我仔细研究  没问题真的count变量改一下别2个都count++  ,  
关于上面哥们说的:     怀疑是这样的,某时刻消费者1抢到锁对象,进入同步代码快,判断条件不符,await()了,就放弃了锁对象,但是它仍处于同步代码区。然后消费者2抢到了锁对象,判断条件也不符,也await()了,放弃了锁对象,它也仍处于同步代码区。下一次恰好生产者1抢到锁对象,判断条件符合,执行完后signal(),这个时候同时唤醒了两个消费者,由于他们都已经在同步代码区,所以他们判断条件符合就并发访问了共享数据count。
请看>>>     消费者try里面的while(!flag) 我们假设消费者1消费一次,然后消费者1又获取权限由于flag=false,等待了,  消费者2获取权限进来也等待了, 就算生产者同时唤醒了消费者2次,消费者1消费一次,flag又等于false这个时候消费者2是唤醒的取得执行权位置在await()那,这个时候注意!!!while(!flag)里面是true那么这个时候就算消费者2执行也要返回判断一下吧,里面是true不又在那await()了,无论如何那个哥们你告诉我2个消费者就算被同时唤醒,他怎么同时执行2次  无论消费者1或2只要消费过一次是不是!flag都变成true了是不是另一个都进while判断了  恩?
回复 使用道具 举报
线程多弄了一次
回复 使用道具 举报
还没有学到这一块。。。。
回复 使用道具 举报

大哥,哪个地方的线程多弄了一次?
回复 使用道具 举报
我来说说,希望对你有帮助。
首先从程序设计意图上,是要实现多个生产者和多个消费者操作共同资源Res,且每生产一个产品,必须要被消费后,才能再生产。
总体思路没有问题,就是实现起来的时候出了些小问题。

1,count计算器设计得不好,从运行结构可以看出,消费的数目要比生产的多一个,应该生产的数量和消费的数量一致才比较合理。
具体应该把set()方法中的生产输出语句改成,System.out.println(Thread.currentThread().getName()+":"+"生产者*******" + (++count));
然后再把out()方法中的消费输出语句改成,System.out.println(Thread.currentThread().getName()+":"+"*******消费者*******" + count);

2,Producer和Consumer类中的循环语句的条件无法限定生产和消费的数量,当然如果只是单纯想让程序不要无限循环这样设置是没问题的。
否则,限定生产消费数量就必须在循环内部额外进行判断,因为等待和唤醒后的操作都是在大循环中完成的,无法判断到大循环上设置的条件。

3,书写规范问题,类名首字母需要大写。

下面是修改后的代码:
  1. import java.util.concurrent.locks.Condition;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;

  4. public class WaitAndSignalByCount {

  5.         public static void main(String[] args) {
  6.                 Res r = new Res();
  7.                 Produce pro = new Produce(r);
  8.                 Consumer con = new Consumer(r);

  9.                 Thread t1 = new Thread(pro);
  10.                 Thread t2 = new Thread(pro);
  11.                 Thread t3 = new Thread(con);
  12.                 Thread t4 = new Thread(con);

  13.                 t1.start();
  14.                 t2.start();
  15.                 t3.start();
  16.                 t4.start();
  17.         }
  18. }

  19. class Res {
  20.         private boolean flag = false;
  21.         public static int count = 0;
  22.         private Lock lock = new ReentrantLock();
  23.         private Condition con_set = lock.newCondition();
  24.         private Condition con_out = lock.newCondition();

  25.         public void set() {
  26.                 lock.lock();
  27.                 try {
  28.                         while (flag && count<100000)
  29.                                 con_set.await();        //-->0,1 10000
  30.                         if (count>=100000){
  31. //                                生产数量已到,唤醒本方所有线程,让本方所有线程能够结束。
  32.                                 con_set.signalAll();
  33. //                                当前线程返回大循环判断条件。
  34.                                 return;
  35.                         }
  36.                         System.out.println(Thread.currentThread().getName()+":"+"生产者*******" + (++count));
  37.                         flag = true;
  38.                         con_out.signal();
  39.                 } catch (Exception e) {
  40.                         System.out.println("*****************************");
  41.                 } finally {
  42.                         lock.unlock();
  43.                 }
  44.         }

  45.         public void out() {
  46.                 lock.lock();
  47.                 try {
  48.                         while (!flag && count<100000)
  49.                                 con_out.await();        //-->2,3
  50.                         if (count>100000)
  51.                                 return;
  52.                         System.out.println(Thread.currentThread().getName()+":"+"*******消费者*******" + count);
  53.                         flag = false;
  54.                         con_set.signal();
  55.                         if(count>=100000){
  56.                                 count++;
  57. //                                消费数量已到,唤醒本方所有线程,让本方所有线程能够结束。
  58.                                 con_out.signalAll();
  59. //                                当前线程结返回大循环判断条件。
  60.                                 return;
  61.                         }
  62.                 } catch (Exception e) {
  63.                         System.out.println("*****************************");
  64.                 } finally {
  65.                         lock.unlock();
  66.                 }
  67.         }
  68. }

  69. class Produce implements Runnable {
  70.         Res r;
  71.         Produce(Res r) {
  72.                 this.r = r;
  73.         }

  74.         public void run() {

  75.                 while (Res.count<100000) {
  76.                         r.set();
  77.                 }
  78.         }
  79. }

  80. class Consumer implements Runnable {
  81.         Res r;
  82.         Consumer(Res r) {
  83.                 this.r = r;
  84.         }

  85.         public void run() {

  86.                 while (Res.count<=100000) {
  87.                         r.out();
  88.                 }
  89.         }
  90. }
复制代码



回复 使用道具 举报
没找出问题的原因
回复 使用道具 举报
怀疑是这样的,某时刻消费者1抢到锁对象,进入同步代码快,判断条件不符,await()了,就放弃了锁对象,但是它仍处于同步代码区。然后消费者2抢到了锁对象,判断条件也不符,也await()了,放弃了锁对象,它也仍处于同步代码区。下一次恰好生产者1抢到锁对象,判断条件符合,执行完后signal(),这个时候同时唤醒了两个消费者,由于他们都已经在同步代码区,所以他们判断条件符合就并发访问了共享数据count。不过如果这样,那两个99987下面应该是99989,跳过99988的,这个也没想明白
回复 使用道具 举报
唉,表示即将学到。。。
回复 使用道具 举报
兄弟,要确保现成的安全性,需要在现成执行代码中加入同步,即synchornized的。加入同步可以保证多个现成不会同时对同步代码进行操作,也就避免了安全问题
回复 使用道具 举报
醉翁之意不在酒 来自手机 中级黑马 2015-8-2 07:33:47
11#
三中情况需要用到同步,
回复 使用道具 举报
a12366456 发表于 2015-7-27 17:16
怀疑是这样的,某时刻消费者1抢到锁对象,进入同步代码快,判断条件不符,await()了,就放弃了锁对象,但是 ...

线程不unlock释放锁另一个消费者进的来?
回复 使用道具 举报
boboyuwu 发表于 2015-8-2 19:45
线程不unlock释放锁另一个消费者进的来?

因为它await了,就放弃了锁对象,而这几个线程是共用一个锁对象,其他线程就可以进来同步代码区了。某个线程的推进顺序下刚好两个消费者线程await了,又刚好被生产者唤醒,同时这两个消费者可不会从代码的最开始处执行,而是从await的地方开始执行。所以,应该让消费者之间也互斥访问共享数据。
回复 使用道具 举报
用你程序测试多遍没问题你debug再试试?
package text;



import java.util.concurrent.locks.*;


class Res
{
private boolean flag =  false;
public static int count = 0;
private Lock lock = new ReentrantLock();
private Condition con_set = lock.newCondition();
private Condition con_out = lock.newCondition();

public void set()
{
  lock.lock();
  try
  {
   while(flag)
    con_set.await();
   System.out.println("生产者>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+(++count));
   flag = true;
   con_out.signal();
  }
  catch(Exception e)
  {
   System.out.println("*****************************");
  }
  finally
  {
   lock.unlock();
  }
}

public void out()
{
  lock.lock();
  try
  {
   while(!flag)
    con_out.await();
   System.out.println("*******消费者*******"+count);
   flag = false;
   con_set.signal();
  }
  catch(Exception e)
  {
   System.out.println("*****************************");
  }
  finally
  {
   lock.unlock();
  }
}
}
class produce implements Runnable
{
Res r;
produce(Res r)
{
  this.r=r;
}
public void run()
{
  
  
   while(Res.count<=100000)
   {
    r.set();
   }
  
}
}
class consumer implements Runnable
{
Res r;
consumer(Res r)
{
  this.r=r;
}
public void run()
{
  
   while(Res.count<=100000)
   {
    r.out();
   }
  
  }
}
public class text{
public static void main(String[] args)
{
  Res r = new Res();
  produce pro = new produce(r);
  consumer con = new consumer(r);
  
  Thread t1 = new Thread(pro);
  Thread t2 = new Thread(pro);
  Thread t3 = new Thread(con);
  Thread t4 = new Thread(con);
  
  t1.start();
  t2.start();
  t3.start();
  t4.start();
}
}
回复 使用道具 举报
a12366456 发表于 2015-8-2 21:43
因为它await了,就放弃了锁对象,而这几个线程是共用一个锁对象,其他线程就可以进来同步代码区了。某个 ...

signal()
          唤醒一个等待线程。  看到了么  我想知道生产者1怎么唤醒2个线程
回复 使用道具 举报
boboyuwu 发表于 2015-8-2 22:04
signal()
          唤醒一个等待线程。  看到了么  我想知道生产者1怎么唤醒2个线程 ...

是的,不好意思,这个我说错了
回复 使用道具 举报
必须顶一个
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马