Java多线程 - 线程间通信问题,大家进来看看。

铁衣 2013-06-04 10:49:42
首先附上我写的一个例子:(请大家不要嫌代码长,代码结构很简单的)
public class TestVolatile implements Runnable {
private int n;
private TestWorks work;

public TestVolatile(TestWorks work, int n) {
this.work = work;
this.n = n;
}

public static void main(String[] args) {
TestWorks work = new TestWorks();
new Thread(new TestVolatile(work, 1)).start();
new Thread(new TestVolatile(work, 2)).start();
}

public void run() {
try {
if (1 == n) {
work.work();
} else if (2 == n) {
Thread.sleep(5000);
work.stop();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class TestWorks {
private boolean isWorkEnable = true;

public void stop() {
isWorkEnable = false;
}
public void work() throws InterruptedException {
while (isWorkEnable) {
System.out.println("------>服务正在运行!");
Thread.sleep(1000);
}
System.out.println("------>服务已经停止!");
}
}
运行结果:
------>服务正在运行!
------>服务正在运行!
------>服务正在运行!
------>服务正在运行!
------>服务正在运行!
------>服务已经停止!

从运行结果可以看出,[线程2]调用的stop()方法影响到了[线程1]的运行。
TestWorks类的isWorkEnable属性对于[线程1]和[线程2]是可见的。

这个实验结果让我很困惑。
因为,我之前在很多地方都看到过这样的描述:
1.每个线程启动的时候,都会创建一片属于自己的工作内存。
这片工作内存是独占式的,其他线程不能访问当前线程所属的工作内存里的变量值。
2.线程运行的时候,会把主存中的变量拷贝一份到自己的工作内存中。
线程运行期间,对变量的操作只针对工作内存中的拷贝,而不是直接修改主存。
当线程结束了,才会把工作内存中的变量值同步到主存中。
当主存中的变量值同步完成以后,当前线程对变量的修改对于其他线程才是可见的。


按照上面描述的两点,我的这个实验的预期结果应该是:
[线程2]调用的stop()方法不会影响到[线程1]的运行,[线程1]应该一直运行下去。

但事实不是这样的,请高手帮我解答一下,这个问题困扰我好几天了。
我的学习进程也因此中断了。


最后顺便说一点,我写这个例子的最初目的是为了验证volatile变量的实际用途。
因为我觉得TestWorks类的isWorkEnable变量如果不加volatile修饰的话,那么[线程2]是不应该影响到[线程1]的。isWorkEnable加上volatile才会让isWorkEnable变量对于两个线程都是可见的。
...全文
291 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
dddddz 2013-06-05
  • 打赏
  • 举报
回复
[线程2]调用的stop()方法修改完isWorkEnable 的值后,线程就结束了,线程结束前会把isWorkEnable 的值更新到主存。因此新的isWorkEnable值对 线程1可见
铁衣 2013-06-05
  • 打赏
  • 举报
回复
引用 10 楼 shnulaa 的回复:
这里有个例子可以说明Volatile的问题

public class VolatileTest {

	private volatile static int MY_INT = 0;

	public static void main(String[] args) {
		new ChangeListener().start();
		new ChangeMaker().start();
	}

	static class ChangeListener extends Thread {
		@Override
		public void run() {
			int local_value = MY_INT;
			while (local_value < 5) {
				if (local_value != MY_INT) {
					System.out.println("Got Change for MY_INT :" + MY_INT);
					local_value = MY_INT;
				}
			}
		}
	}

	static class ChangeMaker extends Thread {
		@Override
		public void run() {

			int local_value = MY_INT;
			while (MY_INT < 5) {
				System.out.println("Incrementing MY_INT to " + (local_value + 1));
				MY_INT = ++local_value;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
在加上volatile执行结果

Incrementing MY_INT to 1
Got Change for MY_INT :1
Incrementing MY_INT to 2
Got Change for MY_INT :2
Incrementing MY_INT to 3
Got Change for MY_INT :3
Incrementing MY_INT to 4
Got Change for MY_INT :4
Incrementing MY_INT to 5
Got Change for MY_INT :5
去掉执行结果

Incrementing MY_INT to 1
Incrementing MY_INT to 2
Incrementing MY_INT to 3
Incrementing MY_INT to 4
Incrementing MY_INT to 5
记得vm 参数加上-server 可以研究一下这个例子,可以说明一定的问题哦。
感谢shnulaa给出的例子。 其实我也觉得我写的例子估计是有问题的,那么怎么写一个例子能正确验证volatile的功能? 这也很头疼。 我去试试你的例子。
铁衣 2013-06-05
  • 打赏
  • 举报
回复
引用 8 楼 ldh911 的回复:
[quote=引用 楼主 carrd2008 的回复:] 因为,我之前在很多地方都看到过这样的描述:
这种描述不够准确,这是为了优化执行效率才会进行的动作。 此外,这种例子一般需要让JVM进行极端优化,至少考虑增加: -server 运行参数。 最后,程序中存在sleep()、IO类操作,也即导致线程需要切换上下文的时候,都很可能引发内存的同步。 所以最好的例子,就是比较干净的死循环,可以参加:《JAVA并发编程实践》这本书,写的比较不错。[/quote] 谢谢ldh911的回复,你的帮助对我非常有用。 我对内存模型的理解又深入了一点。
铁衣 2013-06-05
  • 打赏
  • 举报
回复
引用 7 楼 dr8737010 的回复:
[quote=引用 2 楼 carrd2008 的回复:] [quote=引用 1 楼 dr8737010 的回复:] TestWorks work = new TestWorks(); new Thread(new TestVolatile(work, 1)).start(); new Thread(new TestVolatile(work, 2)).start(); 你这里一个TestWorks对象被两个线程所共享,当然线程1对isWorkEnable变量的修改会影响到线程2了
楼上,你看一下我写的蓝色字体的那两条。 那是我从其他资料中看到的。 上面不是说,线程对变量的操作只是针对工作内存中的拷贝的吗? 线程既然还没结束,怎么会影响到主存中的变量? 如果我对两个线程传入同一个引用就可以实现线程间的通信,那么volatile修饰符又有什么用? 能详细帮我解答一下吗? 谢谢!~[/quote] 你理解的没问题,上面写的代码还是有问题的。 当一个线程欲引用字段的值时,会一次将值从主存储器拷贝到工作存储器上(read->load操作),通过此拷贝得到的值是工作拷贝,拷贝完成以后线程就会使用工作拷贝。 当同一线程再度引用同一字段值,线程或许会引用刚才的所制作的工作拷贝,也可能会重新从主存储器拷贝到工作存储器,然后才引用工作拷贝(read->load->use),具体会出现哪一种状况,是由java执行处理系统所决定的。 指定(赋值)某个字段时,也是同样的道理,指定有可能只针对工作拷贝,也有可能在每次指定字段值以后,拷贝到主存储器(assign->store->write),会执行哪个操作,是由java执行处理系统所决定的。 你这里的代码虽然没有出现问题,但并不代码就不会出现问题,所以isWorkEnable需要用volatile来修饰,它的作用是: 1.进行内存的同步 2.以atomic的方式来进行long和double的指定。 关于volatile用法的文章见此: http://www.ibm.com/developerworks/cn/java/j-jtp06197.html [/quote] 谢谢dr8737010的回复,看了你的说明,我终于弄明白了我的问题。
yktd26 2013-06-05
  • 打赏
  • 举报
回复
楼主那段蓝色的不知道从哪看到了,确实像八楼说的,那段描述不准确,内存的同步不是在线程结束后才进行了, 21世纪了,除非是因为性能问题主动的自愿的构造这么一个环境,否则java要是默认这样处理内存早就被淘汰了 如果你对这方面感兴趣ifeve上几篇文章可以看看 比如说这一篇同步和Java内存模型 (三)可见性
晓风吹雾 2013-06-05
  • 打赏
  • 举报
回复
这里有个例子可以说明Volatile的问题

public class VolatileTest {

	private volatile static int MY_INT = 0;

	public static void main(String[] args) {
		new ChangeListener().start();
		new ChangeMaker().start();
	}

	static class ChangeListener extends Thread {
		@Override
		public void run() {
			int local_value = MY_INT;
			while (local_value < 5) {
				if (local_value != MY_INT) {
					System.out.println("Got Change for MY_INT :" + MY_INT);
					local_value = MY_INT;
				}
			}
		}
	}

	static class ChangeMaker extends Thread {
		@Override
		public void run() {

			int local_value = MY_INT;
			while (MY_INT < 5) {
				System.out.println("Incrementing MY_INT to " + (local_value + 1));
				MY_INT = ++local_value;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
在加上volatile执行结果

Incrementing MY_INT to 1
Got Change for MY_INT :1
Incrementing MY_INT to 2
Got Change for MY_INT :2
Incrementing MY_INT to 3
Got Change for MY_INT :3
Incrementing MY_INT to 4
Got Change for MY_INT :4
Incrementing MY_INT to 5
Got Change for MY_INT :5
去掉执行结果

Incrementing MY_INT to 1
Incrementing MY_INT to 2
Incrementing MY_INT to 3
Incrementing MY_INT to 4
Incrementing MY_INT to 5
记得vm 参数加上-server 可以研究一下这个例子,可以说明一定的问题哦。
BadPattern 2013-06-05
  • 打赏
  • 举报
回复
当线程引用volatile字段时,会发生从主存储器到工作存储器的拷贝。 将指定的值写给volatile字段时,工作存储器的内容会映像到主存储器。 没有出现问题并不代表代码没有问题,一旦出了问题就让人崩溃。
MiceRice 2013-06-05
  • 打赏
  • 举报
回复
引用 楼主 carrd2008 的回复:
因为,我之前在很多地方都看到过这样的描述:
这种描述不够准确,这是为了优化执行效率才会进行的动作。 此外,这种例子一般需要让JVM进行极端优化,至少考虑增加: -server 运行参数。 最后,程序中存在sleep()、IO类操作,也即导致线程需要切换上下文的时候,都很可能引发内存的同步。 所以最好的例子,就是比较干净的死循环,可以参加:《JAVA并发编程实践》这本书,写的比较不错。
BadPattern 2013-06-05
  • 打赏
  • 举报
回复
引用 2 楼 carrd2008 的回复:
[quote=引用 1 楼 dr8737010 的回复:] TestWorks work = new TestWorks(); new Thread(new TestVolatile(work, 1)).start(); new Thread(new TestVolatile(work, 2)).start(); 你这里一个TestWorks对象被两个线程所共享,当然线程1对isWorkEnable变量的修改会影响到线程2了
楼上,你看一下我写的蓝色字体的那两条。 那是我从其他资料中看到的。 上面不是说,线程对变量的操作只是针对工作内存中的拷贝的吗? 线程既然还没结束,怎么会影响到主存中的变量? 如果我对两个线程传入同一个引用就可以实现线程间的通信,那么volatile修饰符又有什么用? 能详细帮我解答一下吗? 谢谢!~[/quote] 你理解的没问题,上面写的代码还是有问题的。 当一个线程欲引用字段的值时,会一次将值从主存储器拷贝到工作存储器上(read->load操作),通过此拷贝得到的值是工作拷贝,拷贝完成以后线程就会使用工作拷贝。 当同一线程再度引用同一字段值,线程或许会引用刚才的所制作的工作拷贝,也可能会重新从主存储器拷贝到工作存储器,然后才引用工作拷贝(read->load->use),具体会出现哪一种状况,是由java执行处理系统所决定的。 指定(赋值)某个字段时,也是同样的道理,指定有可能只针对工作拷贝,也有可能在每次指定字段值以后,拷贝到主存储器(assign->store->write),会执行哪个操作,是由java执行处理系统所决定的。 你这里的代码虽然没有出现问题,但并不代码就不会出现问题,所以isWorkEnable需要用volatile来修饰,它的作用是: 1.进行内存的同步 2.以atomic的方式来进行long和double的指定。 关于volatile用法的文章见此: http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
shaguaanfanga 2013-06-05
  • 打赏
  • 举报
回复
再给点提示:当线程结束了,才会把工作内存中的变量值同步到主存中。
shaguaanfanga 2013-06-05
  • 打赏
  • 举报
回复
懂了,记得给分哦!!!
shaguaanfanga 2013-06-05
  • 打赏
  • 举报
回复
亲!run()方法执行完线程就结束了,接下来看看蓝色的第二条,你懂得!!
乔巴大大 2013-06-05
  • 打赏
  • 举报
回复
引用 1 楼 dr8737010 的回复:
TestWorks work = new TestWorks(); new Thread(new TestVolatile(work, 1)).start(); new Thread(new TestVolatile(work, 2)).start(); 你这里一个TestWorks对象被两个线程所共享,当然线程1对isWorkEnable变量的修改会影响到线程2了
楼上正解,不信可以把 TestWorks work = new TestWorks();放到run()方法里去,这样每个线程就各有一个TestWorks对象,互不影响。
铁衣 2013-06-05
  • 打赏
  • 举报
回复
引用 1 楼 dr8737010 的回复:
TestWorks work = new TestWorks(); new Thread(new TestVolatile(work, 1)).start(); new Thread(new TestVolatile(work, 2)).start(); 你这里一个TestWorks对象被两个线程所共享,当然线程1对isWorkEnable变量的修改会影响到线程2了
楼上,你看一下我写的蓝色字体的那两条。 那是我从其他资料中看到的。 上面不是说,线程对变量的操作只是针对工作内存中的拷贝的吗? 线程既然还没结束,怎么会影响到主存中的变量? 如果我对两个线程传入同一个引用就可以实现线程间的通信,那么volatile修饰符又有什么用? 能详细帮我解答一下吗? 谢谢!~
BadPattern 2013-06-05
  • 打赏
  • 举报
回复
TestWorks work = new TestWorks(); new Thread(new TestVolatile(work, 1)).start(); new Thread(new TestVolatile(work, 2)).start(); 你这里一个TestWorks对象被两个线程所共享,当然线程1对isWorkEnable变量的修改会影响到线程2了

62,621

社区成员

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

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