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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 孙茜茜 中级黑马   /  2013-6-12 00:38  /  2232 人查看  /  13 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 孙茜茜 于 2013-6-12 16:51 编辑
  1. /*
  2. 多个窗口同时卖票
  3. */
  4. class MaiPiao implements Runnable
  5. {
  6.         private static int num=1;//每个对象中不该重复同样的票,静态就全局了
  7.         public void run()
  8.         {
  9.                 synchronized(new Object())
  10.                 {
  11.                         for (;num<=50 ;num++)
  12.                         {
  13.                                 System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");
  14.                         }
  15.                 }
  16.         }
  17. }
  18. class Testmp2
  19. {
  20.         public static void main(String[] args)
  21.         {
  22.                 MaiPiao d=new MaiPiao();
  23.                 Thread x1=new Thread(d,"窗口A");
  24.                 Thread x2=new Thread(d,"窗口B");
  25.                 Thread x3=new Thread(d,"窗口C");
  26.                 x1.start();
  27.                 x2.start();
  28.                 x3.start();
  29.         }
  30. }
复制代码
本该打印50个不同的票号,但是打印结果里总是有某两张票重复打印,一共52行,

而且重复的一般是靠前的票号,像1和2,见图中红圈的地方。
我觉得这不是synchronized能解决的,谁知道是什么原因啊???

图1.PNG (5.67 KB, 下载次数: 0)

图1.PNG

评分

参与人数 1技术分 +1 收起 理由
尹丽峰 + 1 神马都是浮云

查看全部评分

13 个回复

倒序浏览
我来解释解释。
核心问题在于锁与static属性,
static声明的属性表示为全局属性或类属性,性质上属于类,在你的程序中也就属于MaiPiao类,
使用同步时需要获得对象监视器的锁,问题就出在这里了,static num属性属于MaiPiao类,不属于Object,你的同步锁用Object就不合适了,
用MaiPiao.class或this给同步上锁,这才比较合适。
修改代码如下:
  1. import java.io.*;
  2. import java.util.*;

  3. /*
  4. 多个窗口同时卖票
  5. */
  6. class MaiPiao implements Runnable
  7. {
  8.         private static int num=1;//每个对象中不该重复同样的票,静态就全局了
  9.         public void run()
  10.         {
  11.                 synchronized(this)//MaiPiao.class也可以
  12.                 {
  13.                         for (;num<=50 ;num++)
  14.                         {
  15.                                 System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");
  16.                         }
  17.                 }
  18.         }
  19. }
  20. public class J
  21. {
  22.         public static void main(String[] args)
  23.         {
  24.                 MaiPiao d=new MaiPiao();
  25.                 Thread x1=new Thread(d,"窗口A");
  26.                 Thread x2=new Thread(d,"窗口B");
  27.                 Thread x3=new Thread(d,"窗口C");
  28.                 x1.start();
  29.                 x2.start();
  30.                 x3.start();
  31.         }
  32. }
复制代码
回复 使用道具 举报
我有点迷糊关于那个静态 num了
在我看来:
很显然 你用的并不是共享的同一个int num;
你创建了3个线程  new了三个买票对象 应该有三个Num静态对象把。
回头我试验以下 new 3个变量名称和类型都一样的变量,看看是三个变量还是只有一个共享变量。
这里我认为 num不是共享的。


因为如果num是共享的 另一个线程不会开始就是1的


其次,  1

最重要的一点,
你的同步锁 使用的不是一个锁,
三个线程是不同步的
你的锁是

new Object 你想想 三个线程 三个 new Object 那不是有了三个锁了吗?
如上所说 锁可以用 MaiPiao.class  这样用的就是一个锁了。。



再者 也是很重要的一点
从输出就可以出来。
你的同步锁起来的代码不对
按照你的代码
如果你的锁按照上面所说设置正确
那么 一个买票窗口不卖到50张票 第二个窗口是不可能卖票的
因为你同步把   for 循环圈里面了。
一个线程进入了锁 那么 就会执行for循环  他for循环不执行完  他不会解锁 别的线程别想运行。

可是因为你锁设置的不对 所以才会出现 第一个窗口才卖了 9张票 第二个窗口就卖了。

所以 关于第一个静态num  我实在是模糊了  我觉得你的静态num 肯定有3个  我建议你把 静态num定义在主函数所在的类中   然后通过 类名.变量名 调用

关于锁对象  我可以肯定你用错了。

还有锁起来的代码,应该这样:


              
               
                        for (;num<=50 ;num++)
                        {
                                 synchronized(this)//MaiPiao.class也可以
                                 {
                                             System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");
                                 }
                        }
               

回复 使用道具 举报
mvplee 发表于 2013-6-12 01:03
我来解释解释。
核心问题在于锁与static属性,
static声明的属性表示为全局属性或类属性,性质上属于类,在 ...

不正确、
回复 使用道具 举报
  1. package thread;

  2. class MyTicketss implements Runnable
  3. {
  4.         private int num = 50;//50张票
  5.         @Override
  6.         public void run() {
  7.                         for (int i = 0; i < 100; i++) {//卖100次。多少次其实不影响!
  8.                                 synchronized(this){
  9.                                 if(num>0){
  10.                                         try {
  11.                                                 Thread.sleep(1);//模拟延迟,导致意外的发生
  12.                                         } catch (InterruptedException e) {
  13.                                                 e.printStackTrace();
  14.                                         }        System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票");
  15.                                 }
  16.                 }}
  17.         }
  18. }

  19. public class TicketAlter {
  20.         public static void main(String[] args) throws InterruptedException {
  21.                 MyTicketss m = new MyTicketss();
  22.                 new Thread(m,"A").start();
  23.                 new Thread(m,"B").start();
  24.                 new Thread(m,"C").start();
  25.         }
  26. }
复制代码
没看出哪里错,除了this那点,上面两位修改的都不完全正确!
向别人展示(工作中)的代码要保证正确,就是自己确认无误的,见过的惯用法!!加油黑马人!!
回复 使用道具 举报
mvplee 发表于 2013-6-12 01:03
我来解释解释。
核心问题在于锁与static属性,
static声明的属性表示为全局属性或类属性,性质上属于类,在 ...

你这只有A窗口在运行
回复 使用道具 举报
尹桥印 发表于 2013-6-12 09:32
一句话:你的锁不是一个锁。
怎么能每次都new Object(),你要先new出来,用的时候把Object这个对象传入 ...

这个回答才是最正确的
回复 使用道具 举报
Super_Class 发表于 2013-6-12 10:49
这个回答才是最正确的

可是我改成synchronized(this)或者synchronized(已经new好的b对象)问题就更费解了,出现的情况是全程只有一个线程跑,这样就输出了指定的张数,不会多出几张了。
而且无论总张数多大,都是只有一个线程在跑。
我已经一团糟了
回复 使用道具 举报
本帖最后由 孙茜茜 于 2013-6-12 14:11 编辑

锁的问题我改了两种,并把总票数改成1000便于观察,现象是这样:
synchronized的参数写new Object()时,运行结果有1002行,就是说某两张票是重复的,并且经多次试验,重复的票号都是靠前的数字。无论设定张数改多少,输出总是比它多两行。
像这样:
窗口A卖出: 1 号票
窗口A卖出: 2 号票
窗口A卖出: 3 号票
窗口A卖出: 4 号票  重复票
窗口B卖出: 4 号票  重复票
窗口B卖出: 6 号票  重复票

窗口B卖出: 7 号票
窗口A卖出: 5 号票
窗口B卖出: 8 号票
窗口C卖出: 6 号票  重复票
窗口B卖出: 10 号票
窗口A卖出: 9 号票
窗口B卖出: 12 号票
窗口C卖出: 11 号票
..........

而像老师视频那样synchronized的参数里写前面new好的b时,就只有A一个线程运行,输出1000行,程序结束。其实可能不是锁的问题。。。。是while的问题吗???
老师写是run是这样:  (而且这里synchronized的参数写new Object()也没有问题。)
  1. public void run()
  2. {
  3.         while(true)
  4.         {
  5.                 synchronized(b)
  6.                 {
  7.                         if(num<=1000)
  8.                         {
  9.                                 System.out.println(Thread.currentThread().getName()+"卖出:"+(num++)+" 号票");
  10.                         }
  11.                 }
  12.         }
  13. }
复制代码
回复 使用道具 举报
楼主:
1、很汗颜,咋一看,没错啊,接着调试,又想特性,N次后才明白为什么。
2、先说正确的代码吧,经过我调试的。

  1. <P>/*
  2. 多个窗口同时卖票
  3. */
  4. class MaiPiao implements Runnable
  5. {
  6.         private static int num=1;//每个对象中不该重复同样的票,静态就全局了
  7.   public void run()
  8.         {
  9.                
  10.                for (;num<=50 ;num++)//num++不是线程对其的操作,因此不用放到锁里;同时这里的num<=50是第一重判断
  11.                {
  12.                        synchronized(MaiPiao.class)  //用MaiPiao.class   就保证了x1  x2  x3用的是同一个锁
  13.                       { </P>
  14. <P>                              if(num<=50)//这里是第二重判断,原因:假设x1拿到锁,此时num=50,正常执行;同时锁外,x2 或x3也拿到了num=50,等待进锁;</P>
  15. <P>                                                //没有if判断的话,等x1释放完锁,x2直接进锁而不会去for那里判断,然后执行锁里内容,打印51</P>
  16. <P>                                                //同理,x3进锁打印52。有了if就能杜绝这种情况。
  17.                               {
  18.                                      System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");//线程对共享数据num的操作就这一句,因此锁住
  19.                               }
  20.                        }
  21.                 }
  22.                  
  23.         }
  24. }
  25. class Testmp2
  26. {
  27.         public static void main(String[] args)
  28.         {
  29.                 MaiPiao d=new MaiPiao();
  30.                 Thread x1=new Thread(d,"窗口A");
  31.                 Thread x2=new Thread(d,"窗口B");
  32.                 Thread x3=new Thread(d,"窗口C");
  33.                 x1.start();
  34.                 x2.start();
  35.                 x3.start();
  36.         }
  37. } </P>
复制代码
3、需要了解的几个关键点:
      synchronized(对象):在这里有两层含义,一是加锁,线程拿到锁后,不执行完里面代码块,不会释放锁,同时其他线程谁也别想进来;二是  对象,注意,如果就一个线程,那么该对象是谁无所谓,如果是多个线程操作一个共享数据的话,那么这里就要注意了,多个线程用的synchronized  对象必须是是同一个,一定注意是唯一的,必须是同一个。再注意,多次new的对象,不是同一个对象。
      synchronized(对象):锁里面的内容要选择好,一定是线程对数据的操作,如果不是,就不要放进来。
      额外话:当一个类进内存会产生  类.class对象   ,是唯一的,视频里老师有讲。所以我一般用这个。
4、对于楼主代码的解析:

  1. <OL>
  2. <LI>private static int num=1;//每个对象中不该重复同样的票,静态就全局了
  3. </LI>
  4. <LI>        public void run()

  5. <LI>        {

  6. <LI>                synchronized(new Object())//三个线程每执行一次就new一个新对象,锁不唯一

  7. <LI>                {

  8. <LI>                        for (;num<=50 ;num++)//线程没有操作该语句,不能放到锁里。同时,线程在执行完锁内所有代码后才释放锁。

  9. <LI>                        {

  10. <LI>                                System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");

  11. <LI>                        }

  12. <LI>                }

  13. <LI>        }
  14. </LI></OL>
  15. <P> </P>
复制代码
5、我认为这样已经比较详细了,楼主可明白了?

回复 使用道具 举报
孔雀东南飞 发表于 2013-6-12 15:26
楼主:
1、很汗颜,咋一看,没错啊,接着调试,又想特性,N次后才明白为什么。
2、先说正确的代码吧,经过我 ...

很详细!!我也拿你的试了几遍,"num++不是线程对其的操作"这个解释我再琢磨会
回复 使用道具 举报
孔雀东南飞 发表于 2013-6-12 15:26
楼主:
1、很汗颜,咋一看,没错啊,接着调试,又想特性,N次后才明白为什么。
2、先说正确的代码吧,经过我 ...
  1. public void run()
  2. {               
  3.            for (;num<=50 ;num++)
  4.            {
  5.                    try{Thread.sleep(20);}catch(Exception e){}//<span style="background-color: rgb(255, 255, 255);">手动睡眠</span>
  6.                    synchronized(MaiPiao.class)  
  7.                   {
  8.                           if(num<=50)
  9.                           {
  10.                                 System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");
  11.                           }
  12.                   }
  13.                   try{Thread.sleep(20);}catch(Exception e){}//<span style="background-color: rgb(255, 255, 255);">手动睡眠</span>
  14.         }                 
  15. }
复制代码
我折腾了下,又发现问题,在for里锁的前后手动睡眠,时间设大点,这样一般都是两个线程睡那儿等第三个打印,输出是下面的规律
窗口A卖出-1-号票
窗口C卖出-1-号票
窗口B卖出-1-号票
窗口A卖出-4-号票
窗口C卖出-4-号票
窗口B卖出-4-号票
窗口A卖出-7-号票
窗口C卖出-7-号票
窗口B卖出-7-号票
窗口A卖出-10-号票
窗口C卖出-10-号票
窗口B卖出-10-号票
......

回复 使用道具 举报
抱歉,之前虽然运行结果对了,但仔细再看看,还真有问题。
问题在于,对于num的值,只能在某一时刻只能由一个线程来访问。在我上边的代码里,把num放到锁外是错误的,如果放到锁外,会出现重复值的情况,尤其是sleep较长时间后。我又想了下,把控制循环的局部变量单独提出来,另外把num放到锁里面,这样就可以了。
  1.         public void run()
  2.         {               
  3.                    for (int x=1;x<=50 ;x++)
  4.                    {
  5.                            try
  6.                         {
  7.                                 //Thread.sleep(20);//手动睡眠
  8.                         }catch(Exception e)
  9.                         {
  10.                         }

  11.                            synchronized(MaiPiao.class)  
  12.                           {
  13.                                   if(num<=50)
  14.                                   {
  15.                                                 System.out.println(Thread.currentThread().getName()+"卖出-"+num+"-号票");
  16.                                   }
  17.                                 num++;
  18.                           }

  19.                            try
  20.                         {
  21.                                 //Thread.sleep(20);//手动睡眠
  22.                         }catch(Exception e)
  23.                         {
  24.                         }
  25.                 }                 
  26.         }
复制代码
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马