返回信息流public class Test implements Runnable {
int b = 100;
public synchronized void m1() throws Exception {
b = 1000;
Thread.sleep(5000);
System.out.println("b== " + b);
}
public synchronized void m2() throws Exception {
Thread.sleep(2500);
b = 2000;
//System.out.println("m2: " + b);
}
public void run() {
try {
m1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
Test test = new Test();
Thread t = new Thread(test);
t.start();
test.m2();
System.out.println(test.b);
}
}
如果m2()方法不执行那句输出,结果打印:
1000
b== 1000
如果m2()方法执行那句输出,结果打印为:
m2: 2000
2000
b==1000
只是加了一句输出,为什么会造成程序中最后那句System.out.println(test.b)输出的结果不一样呢?
这是一条镜像帖。来源:北邮人论坛 / java / #44718同步于 2015/10/17
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖
[问题]java线程小问题求教
llsrlmz
2015/10/17镜像同步3 回复
订阅后,新回复会通过你的通知中心匿名送达。
3 条回复
m2里有没有println对结果没有决定性的影响。你看到的只是一种可能的结果。结果有可能是几种之中的一个。
这个程序里,三个println有几种可能的执行顺序。而且main里面最后一行读的test.b,读到1000和读到2000都是可能的。根本原因是main里在没有获取test的锁的前提下就去读test.b,和另外两个写冲突了。容我慢慢解释:
留意一下Java里的happen-before关系。
1. 一个线程里,先执行的语句、表达式happen before后执行的。
2. 多个线程之间,如果两个线程去获取同一把锁(就是那个synchronized的作用,进入方法时获取锁,退出方法时释放锁),那么释放锁的动作happen before它之后获得那把锁的动作。
注意:sleep不影响happen-before关系!!!系统随时可以让任何线程在任何时间睡任何时长的觉,所以那几个sleep对结果没有决定性的影响。推理程序执行的结果一定要用happen-before关系,不要依赖“时间”。
对于非volatile的变量的读:一个读操作r能读到的值,必须是另一个写操作w写入的,并且必须满足下面两个条件:
1. 不能是w happen-after r(即,如果w在r之后发生,那么r一定读不到w)
2. 不能有另一个w',使得:w happens-before w' happens-before r(即,如果有另一个w'夹在这个w和r之间,那么w'会遮蔽w的效果)
先说说三个打印(println)的顺序:
println内部实现中会去获得一个锁,所以可以认为所有的println之间有一个顺序(不是同时往屏幕上写,写出乱七八糟的东西)。下文中,把执行的三个println记为m1.println, m2.println和main.println。
这三个println一共有6种排列。
但是因为m2.println和main.println都是主线程执行的,根据上述规则1,m2.println一定happen-before main.println,为了简练,我写作m2.println <<<< main.println
但是m1.println是另一个线程执行的。虽然m2和m1都有synchronized修饰,会获取锁,但是m1和m2执行的顺序还是不能确定,谁先执行都可以。
所以,m1.println和另外两个println之间的关系无法确定,但我们很肯定m2.println <<<< main.println,所以m1.println只能插他俩的空,即只能有3种结果:
m1.println <<<< m2.println <<<< main.println
m2.println <<<< m1.println <<<< main.println
m2.println <<<< main.println <<<< m1.println
所以三种打印顺序都是可能的。
再说说为什么main里最后一行读test.b有可能读到1000也有可能读到2000。
还是happen-before关系,唯一能确定的happen-before关系就是:
m2里的写 <<<< main里的读 (因为它们是同一个线程)
但是,m1里的写,和m2里的写之间的happen-before关系不确定(取决于m1和m2谁先获取锁),更要命的是m1里的写和main里的读有可能没有happen-before关系。假设m2先获取锁,然后m2执行完了m1又获取锁。但当m1获取锁的时候,主线程并没有停止,它继续执行main函数。这样,m1和main最后一行在并发地执行,所以,没有happen-before关系。
所以,如果 m1里的写 <<<< m2里的写 <<<< main里的读,那么main里一定读到2000。
如果m2里的写 <<<< m1里的写,那么main有可能读到1000也有可能读到2000。因为在main读的时候,m1和m2里的写都能满足“可以看到的值”的条件:都不是happen-after main里的读,而且m2里的写<<<<m1里的写,不会插在m1里的写和main里的读之间。所以,main可以看到1000也可以看到2000。
以上解释了程序的结果。
不过,不建议写lz这样的程序。写并发的程序还是说说要达到什么目的比较好,比如同时更新一个数据,比如两个线程之间互相通信,互相同步,等等。
不管怎么样,对于多线程共享的变量,一个大忌就是有的读、写受锁保护,有的读、写不受锁保护。只要有一个不受锁保护,就把别的都影响了。不论读写,都要用锁同步,除非知道volatile和AtomicInteger怎么用。
【 在 nuanyangyang 的大作中提到: 】
: m2里有没有println对结果没有决定性的影响。你看到的只是一种可能的结果。结果有可能是几种之中的一个。
: 这个程序里,三个println有几种可能的执行顺序。而且main里面最后一行读的test.b,读到1000和读到2000都是可能的。根本原因是main里在没有获取test的锁的前提下就去读test.b,和另外两个写冲突了。容我慢慢解释:
: 留意一下Java里的happen-before关系。
: ...................
非常感谢暖神解答,读了好几遍,涨了不少知识,也让作为小白我发现自己还有不少东西需要学习,再次感谢暖神这么详细的解答帮助![ema11]