多线程的练习

pc0de 2011-04-07 10:29:55
/**
* TestThreads.java
*/
public class TestThreads {


public static void main(String[] args) {
ThreadOne t1 = new ThreadOne();
ThreadTwo t2 = new ThreadTwo();
Thread one = new Thread(t1);
Thread two = new Thread(t2);
one.start();
two.start();
}
}

/**
* Accum.java
*/

class Accum {
private static Accum a = new Accum();
private int counter = 0;
private Accum() { }
public static Accum getAccum() {
return a;
}
public void updateCounter(int add) {
counter += add;
}

public int getCount() {
return counter;
}
}

/**
*
* ThreadOne.java
*/

class ThreadOne implements Runnable{
Accum a = Accum.getAccum();
public void run() {
for( int x = 0; x < 98; x++) {
a.updateCounter(1000);
try {
Thread.sleep(50);
} catch(InterruptedException ex) { }
}
System.out.println("one " + a.getCount());
}
}
/**
*
* ThreadTwo.java
*/
class ThreadTwo implements Runnable{
Accum a = Accum.getAccum();
public void run() {
for( int x = 0; x < 99; x++) {
a.updateCounter(1);
try {
Thread.sleep(50);
} catch(InterruptedException ex) { }
}
System.out.println("two " + a.getCount());
}
}


head first java 的一个练习题,书上说结果因该是
$ java TestThreads
one 98098
two 98099
我觉得这个答案有问题,因为线程分配的方式不同,结果也会不一样。
但不管线程的分配怎么变化,最后打印的那个结果应该是 98*1000+99*1 = 98099 才对,这个我说得没错吧?
可是程序在我的电脑上却得出了很多不同的结果,匪夷所思的结果,请大家帮我分析分析。谢谢!
$ java TestThreads
one 94097
two 94097
$ java TestThreads
one 97099
two 97099
$ java TestThreads
one 98098
two 98099
$ java TestThreads
one 96097
two 96098
$ java TestThreads
one 97099
two 97099
$ java TestThreads
one 97096
two 97097

...全文
254 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
JIESA 2011-04-07
  • 打赏
  • 举报
回复
12#说的很详细
dreamhunter_lan 2011-04-07
  • 打赏
  • 举报
回复
也许改成这样好理解一些:
public void updateCounter(int add) {
synchronized (a) {
counter += add;
}
}
ThreadOne与ThreadTwo的中a都是Accum中的a,main中两个线程one和two竞争的资源是a,如果updateCounter声明为synchronized的,当一个线程执行到updateCounter的时候,就获得a(竞争资源)的一把锁,更新完了再释放锁,让其他线程获得锁继续更新,打印结果不一致是因为线程one更新完了,但是在打印之前时间片到了,切换到two更新,可能还没更新完又切换到one,然后打印~~总之可以认为在同步块外面的任何地方都可能进行切换。

而至于
public synchronized void updateCounter(int add) {
counter += add;
}
相当于
public void updateCounter(int add) {
synchronized (this) {
counter += add;
}
}
当线程进行更新的时候获得的是当前对象(this指向的那个实例)的锁,而我们只是在创建a的时候new了一个Accum,this和a是一个东西(可以用this==a验证一下)。
综上:结果不一致是因为时间片轮换的原因,使用同步的情况下可以保证一致性,不会被覆盖掉。
pc0de 2011-04-07
  • 打赏
  • 举报
回复
刚刚才发现,如果把Accum声明为synchronized ,那么结果是固定不变的,难道用私有的构造函数+静态实例在多核的计算机上发挥不了限制对象实例的功能? 也许这是唯一的解释了。。。大家有没有其它见解呢?
$ java TestThreads
one 98099
two 98099
pc0de 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 z3698563 的回复:]

无论单 核双核都是会出现这种问题.单 核 CPU也是分成几个时间片.抢到资源者便会进入执行.你这两线程会交替抢到资源的,但是他们却使用了同一个变量,就会导致结果和预期不符现象.
[/Quote]
实验证明在单核上跑是没有问题的,原因看9楼
pc0de 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 z3698563 的回复:]

引用 3 楼 dreamhunter_lan 的回复:

如果是单核应该没问题,原因就像LZ说的那样,因为一个时刻只有一个线程在跑,如果是多核一个时刻多线程同时去修改一个值,有可能会覆盖掉一些值~~把updateCounter声明为synchronized看看

+1
线程锁定机制,楼好好好看看书. .join();synchronized关键字都可以.
这问题就好比银行存钱,两个人……
[/Quote]
回到书上看看,才发现不是这样的,请看看Accum的构造函数的声明: private Accum() ,是个私有的,然后用private static Accum a = new Accum()创建静态实例,所以事实上两个线程只会去存取Accum的唯一实例。。所以加上synchronized关键字并没有实质的改变
pc0de 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 dreamhunter_lan 的回复:]

如果是单核应该没问题,原因就像LZ说的那样,因为一个时刻只有一个线程在跑,如果是多核一个时刻多线程同时去修改一个值,有可能会覆盖掉一些值~~把updateCounter声明为synchronized看看
[/Quote]
在虚拟机上实验了下,果然是多核的问题,可是把updateCounter声明为synchronized也解决不了问题, 还是若干个结果
z3698563 2011-04-07
  • 打赏
  • 举报
回复
无论单 核双核都是会出现这种问题.单 核 CPU也是分成几个时间片.抢到资源者便会进入执行.你这两线程会交替抢到资源的,但是他们却使用了同一个变量,就会导致结果和预期不符现象.
z3698563 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 dreamhunter_lan 的回复:]

如果是单核应该没问题,原因就像LZ说的那样,因为一个时刻只有一个线程在跑,如果是多核一个时刻多线程同时去修改一个值,有可能会覆盖掉一些值~~把updateCounter声明为synchronized看看
[/Quote]
+1
线程锁定机制,楼好好好看看书. .join();synchronized关键字都可以.
这问题就好比银行存钱,两个人同时存.使用同一个变量.就是出现问题.这种情况只能是每次只允许一个进入run方法.
致知Fighting 2011-04-07
  • 打赏
  • 举报
回复
Accum 类是单例,两个线程同时在它上面update,这就可能出现更新的值还没写入就被覆盖的情况。因为覆盖是随机的,所以就出现了很多答案。

updateCounter声明为synchronized,应该就可以得到统一的答案了
dreamhunter_lan 2011-04-07
  • 打赏
  • 举报
回复
如果是单核应该没问题,原因就像LZ说的那样,因为一个时刻只有一个线程在跑,如果是多核一个时刻多线程同时去修改一个值,有可能会覆盖掉一些值~~把updateCounter声明为synchronized看看
pc0de 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 lodachi 的回复:]

多线程的问题,以我的智商常常想不通,所以一般我不使用。

如果给两个打印语句都加上断点,每次结果都是:
one 98099
two 98099

好奇怪哦
[/Quote]
谢谢回复,这个断点是什么意思? 是sleep吗?出现众多不同结果,这才奇怪。。。
lodachi 2011-04-07
  • 打赏
  • 举报
回复
多线程的问题,以我的智商常常想不通,所以一般我不使用。

如果给两个打印语句都加上断点,每次结果都是:
one 98099
two 98099

好奇怪哦
pc0de 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 bao110908 的回复:]

counter += a; 实际上有三个操作:

1:获得 counter 的当前值
2:将这个值加上 a 的值求和
3:将计算和写回 counter

下面这个是模拟了一个两个线程在不同时间片中的操作:

Java code
时间片A 时间片B 时间片B 时间片C 时间片D 时间片E

线程A: | 获得……
[/Quote]

试过了
把 public void updateCounter(int add) 改为:
public synchronized void updateCounter(int add)
结果还是不行 还不知道有Java有AtomicInteger这个变量,受教了.
12楼的说法或许可以补充你的观点,谢谢!
pc0de 2011-04-07
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 dreamhunter_lan 的回复:]

也许改成这样好理解一些:
public void updateCounter(int add) {
synchronized (a) {
counter += add;
}
}
ThreadOne与ThreadTwo的中a都是Accum中的a,main中两个线程one和two竞争的资源是a,如果updateCounter声明为synchronized的,当一个线程执行到updateC……
[/Quote]
解说得相当的详细 辛苦了!
  • 打赏
  • 举报
回复
由于操作系统线程调度的不确定性,因此每次运行的结果都可能是不一样的,当然了这肯定不是我们想要的结果。
  • 打赏
  • 举报
回复
counter += a; 实际上有三个操作:

1:获得 counter 的当前值
2:将这个值加上 a 的值求和
3:将计算和写回 counter

下面这个是模拟了一个两个线程在不同时间片中的操作:

         时间片A     时间片B     时间片B        时间片C     时间片D       时间片E

线程A: | 获得counter值:0 | --> | 将值增加1000 = 1000| --> | 将1000写回counter | counter=1000
线程B: | 获得counter值:0 | ----> | 将值增加1 = 1| --------> | 将1写回counter | counter=1


要修改的话,可以把 public void updateCounter(int add) 改为:

public synchronized void updateCounter(int add)

或者使用原子变量 AtomicInteger 来避免加锁:

/** * Accum.java */
class Accum {

private static Accum a = new Accum();

private AtomicInteger counter = new AtomicInteger(0);

private Accum() {
}

public static Accum getAccum() {
return a;
}

public void updateCounter(int add) {
counter.addAndGet(add);
}

public int getCount() {
return counter.get();
}
}

62,612

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧