求助 Java 大佬 synchronized 的问题

2022-02-26 08:25:34 +08:00
 gosidealone

synchronized 加在方法上锁的是对象的实例吗?

public class Test {

    public static void main(String[] args) {
        //Main main = new Main();
        new Thread(()->{
            Main main = new Main();
            main.get();
        }).start();
        new Thread(()->{
            Main main = new Main();
            main.get();
        }).start();
    }

}

class Main{
    private static int i = 0;

    public synchronized void get(){
        i++;
        System.out.println(i);
    }
}

这两个线程执行 get 函数的时候会互斥吗?如果是同一个 Main 对象肯定是输出 1 ,2 ,如果不是同一个对象输出的是 2 ,2 或者 1 ,2 或者 2 ,1 ,这是为什么呢?

4471 次点击
所在节点    Java
33 条回复
wangyu17455
2022-02-26 08:45:10 +08:00
会互斥,所以肯定输出 1 ,2 ,加了 sync 关键字同一时刻只有一个线程能执行这个方法,这是互斥性,方法结束所有的变量被写回主内存,这是可见性
dcsuibian
2022-02-26 08:47:20 +08:00
不太记得了,每个对象本身应该是有一个锁的。
比如
synchronized (this){
}
这种写法,方法上的 synchronized 应该是语法糖
但针对 synchronized static 这种写法,那个锁对象就是类的 class 实例
MakHoCheung
2022-02-26 08:48:39 +08:00
不会互斥,都两个 Main 对象了
wangyu17455
2022-02-26 08:59:29 +08:00
我瞎了,没看见是两个对象,一楼当我没说
Blanke
2022-02-26 09:04:37 +08:00
1. 在 get 方法加锁,锁住的是实例对象,因为 get 方法不是 static
2. 两个线程里如果是同一个 Main 对象,第一个线程会先拿到锁,所以输出会是 1,2 不变
3. 两个线程里如果不是同一个 Main 对象,因为是实例锁,两线程不会互斥。如果 i 不是 static ,那么输出都会是 1 ,这里 i 是 static ,所以两个线程并发的时候,都可能先执行 i++,和输出 i ,所以结果可能是 1,2 、2,2 、2,1 三种情况。
具体说明 3 中的输出顺序:
( 1 )输出 1 ,2
线程 1: 执行 i++;
线程 1: 输出 i ,也就是 1 ;
线程 2: 执行 i++;
线程 2: 输出 i ,也就是 2 ;
( 2 )输出 2 ,2
线程 1: 执行 i++;
线程 2: 执行 i++;
线程 1: 输出 i ,也就是 2 ;
线程 2: 输出 i ,也就是 2 ;
( 3 )输出 2 ,1
线程 2: 执行 i++;
线程 2: 输出 i ,也就是 1 ;
线程 1: 执行 i++;
线程 1: 输出 i ,也就是 2 ;

写的不对的地方请指正
cxshun
2022-02-26 09:11:00 +08:00
不会互斥,首先你的 synchronized 是加在实例方法上面,那么就只有同一个对象的才会被锁住,你这里是两个不同的 main 实例,完全没啥关系。

而至于输出 1,2 或者 2 ,1 或者 2 ,2 是因为原子性和可见性的问题,你可以尝试把 i 的类型换成 AtomicInteger 就可以实现你想要的效果了。当然前提还是同一个实例对象
sutra
2022-02-26 09:49:41 +08:00
上述讨论问题时注意被注释掉的那行代码,提问者可能再问被注释掉那行代码启用后会如何,而对于阅读者,那行会自动无视。
gosidealone
2022-02-26 10:20:54 +08:00
@sutra 那倒没有 真的就是没有被修饰的情况,放在那里只是为了对比
gosidealone
2022-02-26 10:21:36 +08:00
@Blanke
@MakHoCheung
@wangyu17455
谢谢大家的回复
shadow1949
2022-02-26 10:57:01 +08:00
@Blanke
还有可能是 1 ,1
aviator
2022-02-26 11:10:51 +08:00
@wangyu17455 建议捐献 /dog
lueffy
2022-02-26 11:19:08 +08:00
不会互斥, 正好最近在看极客时间专栏 [Java 并发编程实战]
推荐看 3|互斥锁(上):解决原子性问题, 4|互斥锁(下):如何用一把锁保护多个资源
应该可以免费阅读 , 这两篇比较详细地介绍了 synchronized
q1angch0u
2022-02-26 11:33:38 +08:00
可以在方法上加 static 或者在代码块中使用 synchronized (Main.class) {} 锁类哈~
ershierdu
2022-02-26 12:22:05 +08:00
最近在准备春招,这个也算是八股文里的经典题目了(虽然我是前几天才知道的。。)
fly2mars
2022-02-26 13:14:37 +08:00
2,1 是怎么来的,只要有 1 个线程的 i 变为 2 输出后,另一个线程只能输出 2 啊
fanxasy
2022-02-26 13:34:25 +08:00
@fly2mars 原始值是 0..
jeffxjh
2022-02-26 13:39:30 +08:00
跑了一下这段代码 2,2
gosidealone
2022-02-26 14:47:03 +08:00
@jeffxjh 多跑几次
JasonLaw
2022-02-26 14:49:42 +08:00
@Blanke #5
@fly2mars #15

Q:为什么会出现先输出 2 ,再输出 1 呢?
A:Main.i 是共享可变状态,但是两个线程所使用的 lock 是不一样的。因此会出现“线程 1 先执行了 i++,线程 2 也看到了线程 1 所做的改变,线程 2 此时所看见的 i 是 1 ,然后线程 2 执行了 i++和 System.out.println(i),先输出了 2 。但是线程 1 并没有看见线程 2 所做的改变,它所看见的 i 还是 1 ,然后执行 System.out.println(i)输出了 1”这种情况。
fly2mars
2022-02-26 14:57:56 +08:00
@JasonLaw
那当线程 1 看见了线程 2 所做的改变,所以输出 2,2 了是吧

请问线程 1 有没有看见线程 2 的改变,是在 System.out.println(i)这步决定的吗

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/836521

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX