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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 赖龙威 中级黑马   /  2013-10-18 23:04  /  2363 人查看  /  24 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

  1. package it.cast.threadtest;

  2. public class Test29_4 {
  3.         public static void main(String[] args) {
  4.                 Integer sum = 0;
  5.                 for (int i = 0; i < 1000; i++) {
  6.                         new Thread(new Counter(sum)).start();
  7.                 }
  8.         }
  9. }

  10. class Counter implements Runnable{
  11.         Integer sum;
  12.         public Counter(Integer sum) {
  13.                 this.sum = sum;
  14.         }
  15.        
  16.         @Override
  17.         public void run() {
  18.                 synchronized (sum) {
  19.                         sum++;
  20.                         System.out.println(sum);       
  21.                 }
  22.         }
  23. }
复制代码
帮我看看这个代码,为什么输出结果会是1000个1

评分

参与人数 1技术分 +1 收起 理由
周志龙 + 1 很给力!

查看全部评分

24 个回复

倒序浏览
楼主的代码的效果是:
for循环开启1000个线程,
每个线程最后都打印1。
要知道每个线程中都有一个sum,
sum在1000个线程之间是不共享的。

点评

理解有误,看我的理解是否正确,多多指教~  发表于 2013-10-19 01:38

评分

参与人数 1技术分 +1 收起 理由
周志龙 + 1 很给力!

查看全部评分

回复 使用道具 举报
顶楼上,楼主这是想让1000个人抢厕所,一不小心造了1000个厕所,大伙儿都美坏了
回复 使用道具 举报
本帖最后由 我能学编程吗 于 2013-10-19 00:08 编辑

感觉楼上回答的不够详细,说得不够明白,原理分析得不够清楚,我看楼主传的是一个对象,那么1000个Counter对象中的变量sum应该都是同一样才对,为什么不是同一个呢?我觉得楼主应该是不明白这个,你们没有解释清楚。
回复 使用道具 举报
为什么打印的是1啊?

点评

看楼下回复。。。  发表于 2013-10-19 01:25
回复 使用道具 举报
本帖最后由 我能学编程吗 于 2013-10-19 01:39 编辑

楼主的代码这样修改就能实现同步效果了:
        Counter target = new Counter(sum);
        for (int i = 0; i < 5; i++) {
                        new Thread(target).start();
        }
这样就是只有一个Runnable对象,之前的是有1000个Runnable对象。
所以我说楼上的解释不太正确,并不是有1000个sum对象的问题,而是1000个Counter对象的问题。然而,为什么就不可以有1000个Counter对象呢?这个并没有解释出来,即使是用了1000个Counter对象,但是run方法里面的同步块中的sum变量是同一个呀,因为是外面传进来的同一个对象sum呀,既然是同一个为什么不会同步呢?如果死要认为这1000个Counter对象中的sum对象不是同一个的话,我可以修改代码如下,我能保证同步代码块中的同步对象是同一个,并且打印对象sum也是所有线程共享的对象,但是为什么没有起到线程同步作用? 代码如下:
  1. class Demo {
  2.         public static void main(String[] args) {
  3.                 Integer sum = 0;
  4.         for (int i = 0; i < 5; i++) {
  5.                         new Thread(new Counter(sum)).start();
  6.         }
  7.                
  8.         }
  9. }

  10. class Counter implements Runnable{
  11.         
  12.         public static Integer sum;                        // 静态变量,绝对是共享的吧
  13.         public static String flag = "A";        // 静态变量,拿它做同步对象也肯定是同一个吧
  14.         
  15.         public Counter(Integer sum) {
  16.                 Counter.sum = sum;
  17.         }

  18.         @Override
  19.         public void run() {
  20.                 synchronized (flag) {
  21.                         sum++;
  22.                         System.out.println(sum);        
  23.                 }
  24.         }
  25. }
复制代码
输出结果:
1
1
2
3
4
结果不全是1,说明sum是共享对象,不然sum的值只能是1。然而问题是为什么没有起到同步作用,并且这里的同步对象flag也是绝对的同一个对象,没道理不同步呀。
如下图,图1是多个线程执行同一个对象的同步代码,图2是多个线程执行了不同对象的同步代码。所以结论是"同步代码块只能同步同一个对象里的代码"(这个知识老师都没有讲,这是我自己对于运行结果的猜测,并不一定正确,但这是我唯一能想到的结论,不然没法解释了),换句话说对于多个对象里的同步代码,即使同步对象sum相同,也无法起到同步作用,所以楼主的代码都打印了1不是因为有1000个sum,实际上楼主的代码只有一个sum对象,所以楼上的解释有些错了。
图1:


图2:




评分

参与人数 2技术分 +1 黑马币 +9 收起 理由
周志龙 + 1 赞一个!
赖龙威 + 9 很给力! 我记住了,只有一个对象的方法才会.

查看全部评分

回复 使用道具 举报
本帖最后由 ヅ飞飞 于 2013-10-19 12:08 编辑

代码也可以这样改:
  1. package it.cast.threadtest;

  2. public class Test29_4  {
  3.         public static void main(String[] args) {
  4.                 Counter c = new Counter(0);                        //建立一个Counter对象
  5.                 for (int i = 0; i < 1000; i++) {
  6.                         new Thread(c).start();   //创建一千个线程传入参数都是c,也就是说线程执行相同run方法,操作相同的sum。
  7.                 }
  8.         }
  9. }

  10. class Counter implements Runnable{
  11.         Integer sum;
  12.         public Counter(Integer sum) {
  13.                 this.sum = sum;
  14.         }
  15.         
  16.         @Override
  17.         public void run() {
  18.                 synchronized (sum) {
  19.                         sum++;
  20.                         System.out.println(sum);        
  21.                 }
  22.         }
  23. }
复制代码
楼主只要弄清楚,new Thread(实现Runnble接口的类的实例).start();表示:启动一个线程并调用run方法,该线程操作的是该实例的成员属性。该小程序应该就搞定了。

评分

参与人数 1技术分 +1 收起 理由
周志龙 + 1

查看全部评分

回复 使用道具 举报
本帖最后由 我能学编程吗 于 2013-10-19 12:56 编辑

事情没完呢!可能我上面的理解是有误的,有木有高手来指点一下啊,我也迷茫了,我把楼主的Integer对象换成MyInteger对象,其他所有的都没有改,然而同步实现了,这是为什么?下面代码确实是有多个Counter对象的,然后好似同步了,神奇啊!传Intger对象跟MyInteger对象有什么不同吗?
  1. public class Demo {
  2.         public static void main(String[] args) {
  3.                 MyInteger sum = new MyInteger(0);
  4.         for (int i = 0; i < 5; i++) {
  5.                         new Thread(new Counter(sum)).start();
  6.         }
  7.                
  8.         }
  9. }

  10. class Counter implements Runnable{
  11.         
  12.         public MyInteger sum;
  13.         
  14.         public Counter(MyInteger sum) {
  15.                 this.sum = sum;
  16.         }

  17.         @Override
  18.         public void run() {
  19.                 synchronized (sum) {
  20.                         sum.i++;
  21.                         System.out.println(sum.i);        
  22.                 }
  23.         }
  24. }

  25. class MyInteger {
  26.         public int i;

  27.         public MyInteger(int i) {
  28.                 this.i = i;
  29.         }
  30. }
复制代码
回复 使用道具 举报
本帖最后由 我能学编程吗 于 2013-10-19 13:23 编辑

又研究了一下,迷底揭晓,对不起楼主,我最开始的理解是错误的,都打印1的原因确实是因为有1000个不同的sum对象了,通过下面代码能知道所有的sum对象不是同一个,因为如果是同一个的话,主线程中的sum也应该等于1才对。如下代码:
  1. public class Demo {
  2.         public static void main(String[] args) {
  3.                 Integer sum = 0;
  4.         for (int i = 0; i < 5; i++) {
  5.                         new Thread(new Counter(sum)).start();
  6.         }
  7.         try { Thread.sleep(1000); } catch (Exception e) { } // 暂停1秒,让子线程先执行完,再执行主线程下面的代码
  8.         System.out.println("主线程中的sum = " + sum);
  9.         }
  10. }

  11. class Counter implements Runnable{
  12.         
  13.         public Integer sum;
  14.         
  15.         public Counter(Integer sum) {
  16.                 this.sum = sum;
  17.         }

  18.         @Override
  19.         public void run() {
  20.                 sum++;
  21.                 System.out.println("子线程中的sum = " + sum);
  22.         }
  23. }

  24. class MyInteger {
  25.         public int i;

  26.         public MyInteger(int i) {
  27.                 this.i = i;
  28.         }
  29. }
复制代码
输出结果如下:
子线程中的sum = 1
子线程中的sum = 1
子线程中的sum = 1
子线程中的sum = 1
子线程中的sum = 1
主线程中的sum = 0



那为什么这1000个sum不是同一个对象呢?问题在这里,我把Integer对象换成MyInteger对象,其他什么都不改,结果这1000个sum又是同一个对象了,如下代码:
  1. public class Demo {
  2.         public static void main(String[] args) {
  3.                 MyInteger sum = new MyInteger(0);
  4.         for (int i = 0; i < 5; i++) {
  5.                         new Thread(new Counter(sum)).start();
  6.         }
  7.         try { Thread.sleep(1000); } catch (Exception e) { } // 暂停1秒,让子线程先执行完,再执行主线程下面的代码
  8.         System.out.println("主线程中的sum = " + sum.i);
  9.         }
  10. }

  11. class Counter implements Runnable{
  12.         
  13.         public MyInteger sum;
  14.         
  15.         public Counter(MyInteger sum) {
  16.                 this.sum = sum;
  17.         }

  18.         @Override
  19.         public void run() {
  20.                 sum.i++;
  21.                 System.out.println("子线程中的sum = " + sum.i);
  22.         }
  23. }

  24. class MyInteger {
  25.         public int i;

  26.         public MyInteger(int i) {
  27.                 this.i = i;
  28.         }
  29. }
复制代码
输出结果如下:
子线程中的sum = 1
子线程中的sum = 3
子线程中的sum = 2
子线程中的sum = 4
子线程中的sum = 5
主线程中的sum = 5


为什么传Integer就不是同一个对象,而传MyInteger就是同一个对象呢?这里我也不明白,希望有高手指点一下吧!
我开始在6楼(地板)的结论说“同步代码只能同步同一个对象里的代码”,看了上面的实验,似乎“同步代码是可以同步多个对象里的代码”,如果这个结论成立,那为什么我6楼(地板)的代码中,明明是用同一个同步对象的啊,可为什么没有起到同步,因为6楼的打印结果是打印了两次1,这是为什么?

点评

To
这个算重复加分了吗?  发表于 2013-10-20 19:10

评分

参与人数 1技术分 +1 收起 理由
周志龙 + 1 赞一个!

查看全部评分

回复 使用道具 举报
本帖最后由 我能学编程吗 于 2013-10-19 13:59 编辑

还是我自己来解答吧:
在我6楼使用flag做为同步对象的代码中,同步对象明明是同一个,可为什么没有同步?
在第一个线程打印了sum=1后,第二个线程才开始执行Runnable中的构造函数(这是有可能发生的)
public Counter(Integer sum) {
        this.sum = sum;
}
这时传进来的sum值是0,把我们Counter中的静态代码的sum值给改成0了,所以当再次运行run代码时,sum++后结果还是1。

最终结论:楼主,你的代码没有什么错,new 1000个Counter对象也是正确的,你唯一错误的地方是,你不应该传Integer对象做为同步对象,至于为什么,我无法解答,目前我大概的测试了,用基本类型的包装类(如Integer、Float、Long、Byte等)和String对象作为参数传进去都是不行的,因为这些传进去后并不能都指向同一个对象。楼主只有这一个错误,只要你不传这些对象,改传其他的对象应该是没问题的,如上面代码我传了一个自定义的MyInteger,或者你也可以传Java中自带的其他类对象。

管理员啊,你看我这研究的这么细,然而你的申核并不仔细,可以说上面的答案都不是正确答案,然而你给技术分了,我现在的这个解释,你要好好看看,如果觉得对的,是不是要多给几分技术分啊,因为你也得到学习了,说明这问题你也不懂,不然上面的答案你就不会给分了!!
回复 使用道具 举报
本帖最后由 斗胆潇洒 于 2013-10-19 14:11 编辑
我能学编程吗 发表于 2013-10-19 13:16
又研究了一下,迷底揭晓,对不起楼主,我最开始的理解是错误的,都打印1的原因确实是因为有1000个不同的sum ...

这个问题有必要这么复杂化吗:L,
new这么多Runnable本来就不好了,
你这里MyInteger说白了,不就一直在改变 i 吗,
你可以打印Integer的hashCode和MyInteger的hashCode,这个才是同一级的,
如果你改变的像Integer一样是hashCode,也会全是1,哦,或说是同一个值
但是你仅仅改变的是同一个MyInteger容器中的 i 值,i 值总在变,
就像每个线程都在操作同一个地方的 i 值
回复 使用道具 举报
我能学编程吗 发表于 2013-10-19 13:50
还是我自己来解答吧:
在我6楼使用flag做为同步对象的代码中,同步对象明明是同一个,可为什么没有同步?
...

同学你能这么认真回答问题非常好,顶一个!但是不是应该考虑下你的解释是否应该简练一些,让提问者更容易明白你的意思,否则有可能把提问者搞的更晕。
另外关于技术分,只是为了促进大家学习积极性,不用看的太重,管理员也很忙,把每人个的解释都看过验证一边,这,不能把管理员当超人使啊。多参见论坛活动,多回答提问,技术分25不是问题的。
加油,你很优秀。

点评

你说的对!  发表于 2013-10-28 16:15
回复 使用道具 举报 1 0

如果楼主已经解惑,请将帖子改为提问结束
回复 使用道具 举报
斗胆潇洒 发表于 2013-10-19 14:07
这个问题有必要这么复杂化吗,
new这么多Runnable本来就不好了,
你这里MyInteger说白了,不就一直在改 ...

确实new 1000个Counter是不应该的。一时来了兴趣就好好研究,只是想了知道根本原因。
回复 使用道具 举报
来学习学习关于多线程的东西。
回复 使用道具 举报
我能学编程吗 发表于 2013-10-19 18:56
确实new 1000个Counter是不应该的。一时来了兴趣就好好研究,只是想了知道根本原因。 ...

帅哥。 你就是我的偶像! 哈哈!
回复 使用道具 举报
To 金牌黑马 2013-10-20 19:10:19
17#
楼主你好,如果问题已解决请将帖子状态修改为提问结束,如果未解决请继续提问,谢谢合作
如果不会修改请看解释帖:http://bbs.itheima.com/thread-89313-1-1.html
回复 使用道具 举报
本帖最后由 我能学编程吗 于 2013-10-28 16:21 编辑
斗胆潇洒 发表于 2013-10-19 14:07
这个问题有必要这么复杂化吗,
new这么多Runnable本来就不好了,
你这里MyInteger说白了,不就一直在改 ...

虽然时间过去很久了,可还是要回来反驳你一下。
我上面已经有代码证明了传基本类型包装类进去不是同一个对象了,如果是同一样对象,那为什么主线程和子线程打印的对象的值不相同?

照你的“这个才是同一级的”,什么意思?你是想说要看两个对象是否是同一个,看hashCode就行了是吗?你是想说hashCode就是内存地址吗?那干脆把hashCode 叫 memoryCode得了。

当然了口说无凭,下面的代码能证明你是错误的:
  1. String str1 = new String("abc");
  2.                 String str2 = new String("abc");
  3.                 System.out.println(str1.hashCode() == str2.hashCode());        // true
  4.                
  5.                 Integer i1 = new Integer(1);
  6.                 Integer i2 = new Integer(1);
  7.                 System.out.println(i1.hashCode() == i2.hashCode());                // true
复制代码
结论就是:基本类型的包装类和String类,它们覆盖了Object的equals方法和hashCode方法,equals比较的是对象里面的内容,而对于内容相同的对象,它们返回的hashCode值相同。

我觉得我这个回复挺好的,给大家普及了一个基本知识,也很重要的。版主是不是应该给加个技术分啊??
回复 使用道具 举报
本帖最后由 斗胆潇洒 于 2013-10-30 21:02 编辑
我能学编程吗 发表于 2013-10-28 16:14
虽然时间过去很久了,可还是要回来反驳你一下。
我上面已经有代码证明了传基本类型包装类进去不是同一个对 ...

你弄错我的意思了,我的回答,意在找出你那个为什么多线程在Integer和MyInteger下,
会出现不同的结果的真正原因,只是说了MyInteger和Integer都是引用对象(都是引用对象吧,所以我说成同一级,不是说同一对象),
而,一个在操作MyInteger中成员变量 i ,一个只是在操作Integer本身(这样操作 i 和Integer不在同一级嘛)。你的精神可嘉,继续努力。由于北京报道黑马,在租房,没网,所以回复迟了点,望见谅:handshake
回复 使用道具 举报
斗胆潇洒 发表于 2013-10-30 20:43
你弄错我的意思了,我的回答,意在找出你那个为什么多线程在Integer和MyInteger下,
会出现不同的结果的真 ...

首先,恭喜你进入了黑马!听说进黑马的一般是住在八维。
不过,我还是不认同你的观点,”一个在操作MyInteger中成员变量 i ,一个只是在操作Integer本身“
首先,对于对象你就理解错误了,对象是一个大的概念,怎么会有个叫操作Integer本身的说法呢?
只要是一个对象,要么这个对象有属性,要么有方法,要么就是什么都没有。我想你写一个对象的时候也是这样的吧。
所以基于我的疑问,我进了Integer类的源代码,如看new Integer(1);这句代码的构造函数的声明源码如下:
public Integer(int value) {
        this.value = value;
}
从源码可知,构造函数把传进来的值赋值给了this.value,也就是Intege类的成员属性。
那么我的MyInteger.i 跟Integer.value都是对象的成员,只是名字不同,我完全可以取相同的名字。所以。。。
回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马