论坛首页 Java企业应用论坛

TIJ 线程,为什么我老觉得 例程 有问题。我太SB啦还是Bruce出错了

浏览 2955 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-08-30   最后修改:2012-09-24
TIJ 21.4.1 装饰性花园

Entrance.run()方法,++number时,作者进行了同步。我认为,此处根本无需同步,因为,对于每个 Entrance对象,肯定都有自己的 number 域,这个 number 域除了 Entrance对象以外,根本没有其他线程,需要修改它。每个Entrance对象修改自己的 number,各个Entrance对象间互不打扰

即使,在其他线程(即main线程)OrnamentalGarden调用了Entrance.sumEntrances()(从而调用了Entrance.getValue()),这也是 Entrance.run()线程完成后才调用的,所以,顶多
Entrance.getValue()同步下可以理解

有人有同感嘛?难道我SB啦,或者Bruce出错了

package tij4.concurrency;

//: concurrency/OrnamentalGarden.java
import static net.mindview.util.Print.print;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Count {
	private int count = 0;
	private Random rand = new Random(47);

	// Remove the synchronized keyword to see counting fail:
	public synchronized int increment() {
		int temp = count;
		if (rand.nextBoolean()) // Yield half the time
			Thread.yield();
		return (count = ++temp);
	}

	public synchronized int value() {
		return count;
	}
}

class Entrance implements Runnable {
	private static Count count = new Count();
	private static List<Entrance> entrances = new ArrayList<Entrance>();
	private int number = 0;
	// Doesn't need synchronization to read:
	private final int id;
	private static volatile boolean canceled = false;

	// Atomic operation on a volatile field:
	public static void cancel() {
		canceled = true;
	}

	public Entrance(int id) {
		this.id = id;
		// Keep this task in a list. Also prevents
		// garbage collection of dead tasks:
		entrances.add(this);
	}

	public void run() {
		while (!canceled) {
			synchronized (this) {
				++number;
			}
			print(this + " Total: " + count.increment());
			try {
				TimeUnit.MILLISECONDS.sleep(100);
			} catch (InterruptedException e) {
				print("sleep interrupted");
			}
		}
		print("Stopping " + this);
	}

	public synchronized int getValue() {
		return number;
	}

	public String toString() {
		return "Entrance " + id + ": " + getValue();
	}

	public static int getTotalCount() {
		return count.value();
	}

	public static int sumEntrances() {
		int sum = 0;
		for (Entrance entrance : entrances)
			sum += entrance.getValue();
		return sum;
	}
}

public class OrnamentalGarden {
	public static void main(String[] args) throws Exception {
		ExecutorService exec = Executors.newCachedThreadPool();
		for (int i = 0; i < 5; i++)
			exec.execute(new Entrance(i));
		// Run for a while, then stop and collect the data:
		TimeUnit.SECONDS.sleep(3);
		Entrance.cancel();
		exec.shutdown();
		if (!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
			print("Some tasks were not terminated!");
		print("Total: " + Entrance.getTotalCount());
		print("Sum of Entrances: " + Entrance.sumEntrances());
	}
}

--------------------分割线--------------------
2012/9/24更新:
OrnamentalGarden调用了Entrance.sumEntrances()(从而调用了Entrance.getValue()),由于这次执行 和 Entrance.run()的执行是不同的线程,而且,他们都访问 number, 所以,将Entrance.getValue()设置为同步,这是正确的。那么,为什么要将 ++number 设置为同步呢?
原因如下:
“对于一个存在data race的变量,不能只同步写或只同步读,因为这样不能保证得到最新值。对其的所有写和所有读必须要都同步,这样才能读取到该最新的数据”。根据这条原理,原书中代码没错,这确实是需要同步的
   发表时间:2012-08-30   最后修改:2012-08-30
Bruce 没错。这个synchronize不是同步修改,而是同步读取。

这个应该是与java的内存模型有关,也与平台有关。如果是在32位或以上的平台上运行,++中的+1操作是原子的。就是说对一个数值的操作不会被分为2个16位的操作。但是如果在16位系统上运行,就可能会出现低16位被更新了,但是高16位还没有更新,此时有个程序来取这个值,就会得到错误的结果。
0 请登录后投票
   发表时间:2012-08-30  
wumingshi 写道
Bruce 没错。这个synchronize不是同步修改,而是同步读取。

这个应该是与java的内存模型有关,也与平台有关。如果是在32位或以上的平台上运行,++中的+1操作是原子的。就是说对一个数值的操作不会被分为2个16位的操作。但是如果在16位系统上运行,就可能会出现低16位被更新了,但是高16位还没有更新,此时有个程序来取这个值,就会得到错误的结果。


说的没错,但是假设不成立。
但就这个程序来说,没有对Entrance对象的number字段进行并发操作。我也不理解为什么会需要同步。
0 请登录后投票
   发表时间:2012-08-30  
blizzard213 写道
wumingshi 写道
Bruce 没错。这个synchronize不是同步修改,而是同步读取。

这个应该是与java的内存模型有关,也与平台有关。如果是在32位或以上的平台上运行,++中的+1操作是原子的。就是说对一个数值的操作不会被分为2个16位的操作。但是如果在16位系统上运行,就可能会出现低16位被更新了,但是高16位还没有更新,此时有个程序来取这个值,就会得到错误的结果。


说的没错,但是假设不成立。
但就这个程序来说,没有对Entrance对象的number字段进行并发操作。我也不理解为什么会需要同步。

对。
只有一个线程(Entrance.run()所在的线程)对number进行操作,所以,没必要同步。

OrnamentalGarden调用了Entrance.sumEntrances()(从而调用了Entrance.getValue()),由于这次执行 和 Entrance.run()的执行是不同的线程,而且,他们都访问 number, 所有,将Entrance.getValue()设置为同步,这里是正确的。。。但是,这和 ++number设置为同步没有一毛钱关系啊也
0 请登录后投票
   发表时间:2012-08-30  
感觉对线程了解比较OK的兄弟,出来解释下啊......
0 请登录后投票
   发表时间:2012-08-30  
我觉得还是需要同步, 因为如果另外一个线程正在调用getValue尝试去获取值时,刚好 Entrance 本线程需要对它进行修改,那么读线程就会阻塞,直到本线程(写线程)完成对它修改完后,读线程才读到最新的值,如果不进行同步,读线程就会读到旧值...
0 请登录后投票
   发表时间:2012-08-30  
有一种写法叫
e =new Entrance(i) ;
new Thread(e).start()
new Thread(e).start()
new Thread(e).start()
new Thread(e).start()

虽然int是原子性的..这是个例子吧..只是标准用法..具体写法和场景有关..也许在真实环境下人家不这么写的...

16位机器有点牵强...

0 请登录后投票
   发表时间:2012-09-24  
iceman1952 写道
blizzard213 写道
wumingshi 写道
Bruce 没错。这个synchronize不是同步修改,而是同步读取。

这个应该是与java的内存模型有关,也与平台有关。如果是在32位或以上的平台上运行,++中的+1操作是原子的。就是说对一个数值的操作不会被分为2个16位的操作。但是如果在16位系统上运行,就可能会出现低16位被更新了,但是高16位还没有更新,此时有个程序来取这个值,就会得到错误的结果。


说的没错,但是假设不成立。
但就这个程序来说,没有对Entrance对象的number字段进行并发操作。我也不理解为什么会需要同步。

对。
只有一个线程(Entrance.run()所在的线程)对number进行操作,所以,没必要同步。

OrnamentalGarden调用了Entrance.sumEntrances()(从而调用了Entrance.getValue()),由于这次执行 和 Entrance.run()的执行是不同的线程,而且,他们都访问 number, 所有,将Entrance.getValue()设置为同步,这里是正确的。。。但是,这和 ++number设置为同步没有一毛钱关系啊也

上面红色部分,说法不正确
“对于一个存在data race的变量,不能只同步写或只同步读,因为这样不能保证得到最新值。对其的所有写和所有读必须要都同步,这样才能读取到该最新的数据”。从而,根据这条原理,原书中代码没错,这确实是需要同步的
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics