黑马程序员技术交流社区
标题: 多线程的一个题目 [打印本页]
作者: 赖龙威 时间: 2013-10-18 23:04
标题: 多线程的一个题目
- package it.cast.threadtest;
- public class Test29_4 {
- public static void main(String[] args) {
- Integer sum = 0;
- for (int i = 0; i < 1000; i++) {
- new Thread(new Counter(sum)).start();
- }
- }
- }
- class Counter implements Runnable{
- Integer sum;
- public Counter(Integer sum) {
- this.sum = sum;
- }
-
- @Override
- public void run() {
- synchronized (sum) {
- sum++;
- System.out.println(sum);
- }
- }
- }
复制代码 帮我看看这个代码,为什么输出结果会是1000个1
作者: 飘落 时间: 2013-10-18 23:19
楼主的代码的效果是:
for循环开启1000个线程,
每个线程最后都打印1。
要知道每个线程中都有一个sum,
sum在1000个线程之间是不共享的。
作者: 風諾 时间: 2013-10-18 23:36
顶楼上,楼主这是想让1000个人抢厕所,一不小心造了1000个厕所,大伙儿都美坏了
作者: 我能学编程吗 时间: 2013-10-19 00:06
本帖最后由 我能学编程吗 于 2013-10-19 00:08 编辑
感觉楼上回答的不够详细,说得不够明白,原理分析得不够清楚,我看楼主传的是一个对象,那么1000个Counter对象中的变量sum应该都是同一样才对,为什么不是同一个呢?我觉得楼主应该是不明白这个,你们没有解释清楚。
作者: 卑微の小幸福 时间: 2013-10-19 00:50
为什么打印的是1啊?
作者: 我能学编程吗 时间: 2013-10-19 01:23
本帖最后由 我能学编程吗 于 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也是所有线程共享的对象,但是为什么没有起到线程同步作用? 代码如下:- class Demo {
- public static void main(String[] args) {
- Integer sum = 0;
- for (int i = 0; i < 5; i++) {
- new Thread(new Counter(sum)).start();
- }
-
- }
- }
- class Counter implements Runnable{
-
- public static Integer sum; // 静态变量,绝对是共享的吧
- public static String flag = "A"; // 静态变量,拿它做同步对象也肯定是同一个吧
-
- public Counter(Integer sum) {
- Counter.sum = sum;
- }
- @Override
- public void run() {
- synchronized (flag) {
- sum++;
- System.out.println(sum);
- }
- }
- }
复制代码 输出结果:
1
1
2
3
4
结果不全是1,说明sum是共享对象,不然sum的值只能是1。然而问题是为什么没有起到同步作用,并且这里的同步对象flag也是绝对的同一个对象,没道理不同步呀。
如下图,图1是多个线程执行同一个对象的同步代码,图2是多个线程执行了不同对象的同步代码。所以结论是"同步代码块只能同步同一个对象里的代码"(这个知识老师都没有讲,这是我自己对于运行结果的猜测,并不一定正确,但这是我唯一能想到的结论,不然没法解释了),换句话说对于多个对象里的同步代码,即使同步对象sum相同,也无法起到同步作用,所以楼主的代码都打印了1不是因为有1000个sum,实际上楼主的代码只有一个sum对象,所以楼上的解释有些错了。
图1:
图2:

作者: ヅ飞飞 时间: 2013-10-19 12:07
本帖最后由 ヅ飞飞 于 2013-10-19 12:08 编辑
代码也可以这样改:- package it.cast.threadtest;
- public class Test29_4 {
- public static void main(String[] args) {
- Counter c = new Counter(0); //建立一个Counter对象
- for (int i = 0; i < 1000; i++) {
- new Thread(c).start(); //创建一千个线程传入参数都是c,也就是说线程执行相同run方法,操作相同的sum。
- }
- }
- }
- class Counter implements Runnable{
- Integer sum;
- public Counter(Integer sum) {
- this.sum = sum;
- }
-
- @Override
- public void run() {
- synchronized (sum) {
- sum++;
- System.out.println(sum);
- }
- }
- }
复制代码 楼主只要弄清楚,new Thread(实现Runnble接口的类的实例).start();表示:启动一个线程并调用run方法,该线程操作的是该实例的成员属性。该小程序应该就搞定了。
作者: 我能学编程吗 时间: 2013-10-19 12:53
本帖最后由 我能学编程吗 于 2013-10-19 12:56 编辑
事情没完呢!可能我上面的理解是有误的,有木有高手来指点一下啊,我也迷茫了,我把楼主的Integer对象换成MyInteger对象,其他所有的都没有改,然而同步实现了,这是为什么?下面代码确实是有多个Counter对象的,然后好似同步了,神奇啊!传Intger对象跟MyInteger对象有什么不同吗?- public class Demo {
- public static void main(String[] args) {
- MyInteger sum = new MyInteger(0);
- for (int i = 0; i < 5; i++) {
- new Thread(new Counter(sum)).start();
- }
-
- }
- }
- class Counter implements Runnable{
-
- public MyInteger sum;
-
- public Counter(MyInteger sum) {
- this.sum = sum;
- }
- @Override
- public void run() {
- synchronized (sum) {
- sum.i++;
- System.out.println(sum.i);
- }
- }
- }
- class MyInteger {
- public int i;
- public MyInteger(int i) {
- this.i = i;
- }
- }
复制代码
作者: 我能学编程吗 时间: 2013-10-19 13:16
本帖最后由 我能学编程吗 于 2013-10-19 13:23 编辑
又研究了一下,迷底揭晓,对不起楼主,我最开始的理解是错误的,都打印1的原因确实是因为有1000个不同的sum对象了,通过下面代码能知道所有的sum对象不是同一个,因为如果是同一个的话,主线程中的sum也应该等于1才对。如下代码:- public class Demo {
- public static void main(String[] args) {
- Integer sum = 0;
- for (int i = 0; i < 5; i++) {
- new Thread(new Counter(sum)).start();
- }
- try { Thread.sleep(1000); } catch (Exception e) { } // 暂停1秒,让子线程先执行完,再执行主线程下面的代码
- System.out.println("主线程中的sum = " + sum);
- }
- }
- class Counter implements Runnable{
-
- public Integer sum;
-
- public Counter(Integer sum) {
- this.sum = sum;
- }
- @Override
- public void run() {
- sum++;
- System.out.println("子线程中的sum = " + sum);
- }
- }
- class MyInteger {
- public int i;
- public MyInteger(int i) {
- this.i = i;
- }
- }
复制代码 输出结果如下:
子线程中的sum = 1
子线程中的sum = 1
子线程中的sum = 1
子线程中的sum = 1
子线程中的sum = 1
主线程中的sum = 0
那为什么这1000个sum不是同一个对象呢?问题在这里,我把Integer对象换成MyInteger对象,其他什么都不改,结果这1000个sum又是同一个对象了,如下代码:- public class Demo {
- public static void main(String[] args) {
- MyInteger sum = new MyInteger(0);
- for (int i = 0; i < 5; i++) {
- new Thread(new Counter(sum)).start();
- }
- try { Thread.sleep(1000); } catch (Exception e) { } // 暂停1秒,让子线程先执行完,再执行主线程下面的代码
- System.out.println("主线程中的sum = " + sum.i);
- }
- }
- class Counter implements Runnable{
-
- public MyInteger sum;
-
- public Counter(MyInteger sum) {
- this.sum = sum;
- }
- @Override
- public void run() {
- sum.i++;
- System.out.println("子线程中的sum = " + sum.i);
- }
- }
- class MyInteger {
- public int i;
- public MyInteger(int i) {
- this.i = i;
- }
- }
复制代码 输出结果如下:
子线程中的sum = 1
子线程中的sum = 3
子线程中的sum = 2
子线程中的sum = 4
子线程中的sum = 5
主线程中的sum = 5
为什么传Integer就不是同一个对象,而传MyInteger就是同一个对象呢?这里我也不明白,希望有高手指点一下吧!我开始在6楼(地板)的结论说“同步代码只能同步同一个对象里的代码”,看了上面的实验,似乎“同步代码是可以同步多个对象里的代码”,如果这个结论成立,那为什么我6楼(地板)的代码中,明明是用同一个同步对象的啊,可为什么没有起到同步,因为6楼的打印结果是打印了两次1,这是为什么?
作者: 我能学编程吗 时间: 2013-10-19 13:50
本帖最后由 我能学编程吗 于 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:07
本帖最后由 斗胆潇洒 于 2013-10-19 14:11 编辑
这个问题有必要这么复杂化吗:L,
new这么多Runnable本来就不好了,
你这里MyInteger说白了,不就一直在改变 i 吗,
你可以打印Integer的hashCode和MyInteger的hashCode,这个才是同一级的,
如果你改变的像Integer一样是hashCode,也会全是1,哦,或说是同一个值
但是你仅仅改变的是同一个MyInteger容器中的 i 值,i 值总在变,
就像每个线程都在操作同一个地方的 i 值
作者: ヅ飞飞 时间: 2013-10-19 15:09
同学你能这么认真回答问题非常好,顶一个!但是不是应该考虑下你的解释是否应该简练一些,让提问者更容易明白你的意思,否则有可能把提问者搞的更晕。
另外关于技术分,只是为了促进大家学习积极性,不用看的太重,管理员也很忙,把每人个的解释都看过验证一边,这,不能把管理员当超人使啊。多参见论坛活动,多回答提问,技术分25不是问题的。
加油,你很优秀。
作者: 周志龙 时间: 2013-10-19 18:28
如果楼主已经解惑,请将帖子改为提问结束
作者: 我能学编程吗 时间: 2013-10-19 18:56
确实new 1000个Counter是不应该的。一时来了兴趣就好好研究,只是想了知道根本原因。
作者: 習慣性、隱身 时间: 2013-10-19 20:57
来学习学习关于多线程的东西。
作者: 卑微の小幸福 时间: 2013-10-20 00:00
帅哥。 你就是我的偶像! 哈哈!
作者: To 时间: 2013-10-20 19:10
楼主你好,如果问题已解决请将帖子状态修改为提问结束,如果未解决请继续提问,谢谢合作
如果不会修改请看解释帖:http://bbs.itheima.com/thread-89313-1-1.html
作者: 我能学编程吗 时间: 2013-10-28 16:14
本帖最后由 我能学编程吗 于 2013-10-28 16:21 编辑
虽然时间过去很久了,可还是要回来反驳你一下。
我上面已经有代码证明了传基本类型包装类进去不是同一个对象了,如果是同一样对象,那为什么主线程和子线程打印的对象的值不相同?
照你的“这个才是同一级的”,什么意思?你是想说要看两个对象是否是同一个,看hashCode就行了是吗?你是想说hashCode就是内存地址吗?那干脆把hashCode 叫 memoryCode得了。
当然了口说无凭,下面的代码能证明你是错误的:- String str1 = new String("abc");
- String str2 = new String("abc");
- System.out.println(str1.hashCode() == str2.hashCode()); // true
-
- Integer i1 = new Integer(1);
- Integer i2 = new Integer(1);
- System.out.println(i1.hashCode() == i2.hashCode()); // true
复制代码 结论就是:基本类型的包装类和String类,它们覆盖了Object的equals方法和hashCode方法,equals比较的是对象里面的内容,而对于内容相同的对象,它们返回的hashCode值相同。
我觉得我这个回复挺好的,给大家普及了一个基本知识,也很重要的。版主是不是应该给加个技术分啊??
作者: 斗胆潇洒 时间: 2013-10-30 20:43
本帖最后由 斗胆潇洒 于 2013-10-30 21:02 编辑
你弄错我的意思了,我的回答,意在找出你那个为什么多线程在Integer和MyInteger下,
会出现不同的结果的真正原因,只是说了MyInteger和Integer都是引用对象(都是引用对象吧,所以我说成同一级,不是说同一对象),
而,一个在操作MyInteger中成员变量 i ,一个只是在操作Integer本身(这样操作 i 和Integer不在同一级嘛)。你的精神可嘉,继续努力。由于北京报道黑马,在租房,没网,所以回复迟了点,望见谅:handshake
作者: 我能学编程吗 时间: 2013-10-30 22:08
首先,恭喜你进入了黑马!听说进黑马的一般是住在八维。
不过,我还是不认同你的观点,”一个在操作MyInteger中成员变量 i ,一个只是在操作Integer本身“
首先,对于对象你就理解错误了,对象是一个大的概念,怎么会有个叫操作Integer本身的说法呢?
只要是一个对象,要么这个对象有属性,要么有方法,要么就是什么都没有。我想你写一个对象的时候也是这样的吧。
所以基于我的疑问,我进了Integer类的源代码,如看new Integer(1);这句代码的构造函数的声明源码如下:
public Integer(int value) {
this.value = value;
}
从源码可知,构造函数把传进来的值赋值给了this.value,也就是Intege类的成员属性。
那么我的MyInteger.i 跟Integer.value都是对象的成员,只是名字不同,我完全可以取相同的名字。所以。。。
作者: 斗胆潇洒 时间: 2013-11-1 13:11
还在研究对象啊,嗯,对象上你说的对的,那你再去研究下你上面那个MyInteger和Integer在的程序,为什么会是不同结果吧,应该就知道我只是在解决问题,对象只不过打个比方而已,没必要去咬文嚼字,分析结果才是,可能我的解释打的比方不好吧,导致你一直在绕着对象转,没去看上面多线程的结果了,抱歉啊
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |