首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wenb1
V2EX  ›  Java

Java 多线程 synchoronized 关键字问题

  •  
  •   wenb1 · 2018-12-05 07:08:17 +08:00 · 2551 次点击
    这是一个创建于 416 天前的主题,其中的信息可能已经有所发展或是发生改变。
    刚开始学多线程,今天在网上看到了下面这个例子,给出的解释是 static 的锁是在 Counter.class 里,非 static 的锁是在 this object 里,它们两个可以同时使用,所以会导致线程不安全。我想问的是在什么样的具体实例里调用这两个方法会导致线程不安全,比如我有线程 1 和线程 2,我想让线程 1 调用 setter,然后线程 2 再调用 getter。在这种情况下,就算都上 static synchoronized 锁,我也不能保证调度器一定先调用线程 1,再调用线程 2 啊?跟不上锁有什么区别?我可以都不上锁,然后调用线程 1 执行 setter,再调用线程 2 执行 getter。

    求大佬们指点!!多谢了!

    代码如下:

    public class Counter{

    private static int count = 0;

    public static synchronized int getCount(){
    return count;
    }

    public synchoronized setCount(int count){
    this.count = count;
    }

    }

    原文 link: https://javarevisited.blogspot.com/2011/04/synchronization-in-java-synchronized.html
    38 回复  |  直到 2019-01-05 16:00:33 +08:00
    krircc
        1
    krircc   2018-12-05 07:34:55 +08:00 via Android
    fhj
    wenb1
        2
    wenb1   2018-12-05 07:41:41 +08:00
    @krircc 什么意思?
    Aruforce
        3
    Aruforce   2018-12-05 07:48:54 +08:00 via Android
    Synchronized 又不是保证线程调度顺序的,是标识独占的,严格线程的顺序请用 join 方法……至于这样写法对于 count 来说应该不是线程安全的……
    zek
        4
    zek   2018-12-05 08:05:57 +08:00 via Android
    你可以让线程 2 先睡一会儿就能保证了
    lhx2008
        5
    lhx2008   2018-12-05 08:08:54 +08:00 via Android
    为啥一个有 static 一个没 static,好玩么。
    如果你实现的是 counter,那么你需要定义一个计数+1 的方法,然后设置锁,而不是读写各加一个锁,没有任何意义
    wenb1
        6
    wenb1   2018-12-05 08:09:56 +08:00
    @Aruforce 那为什么还需要 Synchronized 呢?
    wenb1
        7
    wenb1   2018-12-05 08:10:22 +08:00
    @zek 那效率不就低了
    wenb1
        8
    wenb1   2018-12-05 08:15:13 +08:00
    @lhx2008 这只是一个例子,用来说明 static 和 non-static 的区别。我的问题不是这个要怎样写,而是我不太明白加锁的意义,我的问题大概是加了锁又不能控制线程调度让我得到想要的结果,为什么还要加锁,我可以不同时使用啊
    Aruforce
        9
    Aruforce   2018-12-05 08:17:22 +08:00 via Android
    @wenb1 实现所谓的线程安全啊……什么样才安全呢?当然是当前资源只有一个线程来使用的状态啊……也就是线程独占……
    lhx2008
        10
    lhx2008   2018-12-05 08:20:36 +08:00 via Android
    @wenb1 你要是顺序调用就不多线程,不多线程就没必要有锁。而且多线程是同时解决同一个问题,而不是有顺序的解决两个问题,后者应当在一个线程里面执行
    lihongjie0209
        11
    lihongjie0209   2018-12-05 08:22:30 +08:00
    ccpp132
        12
    ccpp132   2018-12-05 08:22:41 +08:00
    为了防止你 setter 执行到一半的时候执行 getter,你这个例子里本来就没什么鸟用
    vansl
        13
    vansl   2018-12-05 08:25:34 +08:00 via iPhone   ♥ 2
    建议了解一下互斥与同步的概念。synchronized 只能保证互斥,而你需要的是同步,可以通过设置标志位解决。
    lihongjie0209
        14
    lihongjie0209   2018-12-05 08:25:47 +08:00   ♥ 1
    上面代码的关键部分是:

    counter.setCount(Counter.getCount() + 1);



    set 方法拿到的是一个对象锁, 对于 10 个线程来说, 每一个都是 new 一个对象的, 所以每一个都独占这个对象锁.每一个线程都无须等待就可以直接修改变量, 是线程不安全的.

    get 方法拿到的是一个类锁, 对于 10 个线程来说, 每一个线程都必须抢占这个锁, 所以 get 是线程安全的.
    lhx2008
        15
    lhx2008   2018-12-05 08:27:02 +08:00 via Android
    @lihongjie0209 然而读写都是原子操作,所以不加锁也是安全的
    lihongjie0209
        16
    lihongjie0209   2018-12-05 08:29:40 +08:00
    @lhx2008 错, 读是原子操作, 写是原子操作, 读+写时复合操作, 不保证原子性
    lihongjie0209
        17
    lihongjie0209   2018-12-05 08:30:16 +08:00
    @lhx2008 多运行几次代码就会发现最后的结果在 9 和 10 之前摇摆
    azhangbing
        18
    azhangbing   2018-12-05 08:31:36 +08:00 via iPhone
    饿 你想实现的是生产者和消费者模式吧
    lhx2008
        19
    lhx2008   2018-12-05 08:33:20 +08:00 via Android   ♥ 1
    @lihongjie0209 复合操作,不是类提供的,如果是类提供的,要加锁,外部调用是外部调用者的责任,他应该在执行这行代码前后加锁
    lihongjie0209
        20
    lihongjie0209   2018-12-05 08:33:24 +08:00
    @vansl 互斥只是一个退化版本的同步
    vansl
        21
    vansl   2018-12-05 08:34:10 +08:00 via iPhone   ♥ 1
    @lhx2008 没有用 volatile 修饰还是会出现内存可见性问题
    wenb1
        22
    wenb1   2018-12-05 08:34:17 +08:00
    @vansl 我觉得你说的非常有道理!我去看看,多谢了!!
    lhx2008
        23
    lhx2008   2018-12-05 08:35:35 +08:00 via Android
    @lihongjie0209 不过 9 和 10,这个,明明是这个代码写的太蠢了。。你加不加锁都一样
    lhx2008
        24
    lhx2008   2018-12-05 08:36:09 +08:00 via Android
    @vansl 是的,忘了这个
    wenb1
        25
    wenb1   2018-12-05 08:40:35 +08:00
    @lihongjie0209 多谢!我对这个例子有了更深的理解!
    lihongjie0209
        26
    lihongjie0209   2018-12-05 08:40:36 +08:00
    @lhx2008 废话, 没看到题主说的: 我想问的是在什么样的具体实例里调用这两个方法会导致线程不安全?
    lihongjie0209
        27
    lihongjie0209   2018-12-05 08:52:28 +08:00
    @vansl 这段代码会一直运行, 哪怕有 volatile 修饰也会有内存可见性问题
    Ico945
        28
    Ico945   2018-12-05 08:53:56 +08:00
    锁保证的是共享区(变量)一次只能由一个线程操作,并不能保证线程的执行顺序
    MoHen9
        29
    MoHen9   2018-12-05 08:57:11 +08:00 via Android
    synchronized 是信号量,状态被放在对象头里面, 我们常说被 static 修饰的方法属于类,内存中有唯一的一个 Class 对象对应着当前类,而这个对象的对象头存放着被 static 修饰的 synchronized 的状态,没有 static 修饰的方法,synchronized 的状态存放在当前对象中。所以,你这是两把锁,想要保证同一个变量的安全。你可以都加 static,或者都不加,或者使用一个单独的对象锁存放 synchronized 的状态。
    araraloren
        30
    araraloren   2018-12-05 08:59:45 +08:00
    楼主可以了解一下啥叫互斥,啥叫同步
    sagaxu
        31
    sagaxu   2018-12-05 09:01:39 +08:00 via Android   ♥ 1
    @lhx2008 long 类型的写入在某些 arch 下不是原子操作。即使是原子操作,也需要某种同步方式解决内存可见性问题。同一个线程内代码顺序建立 happens-before 关系,不同线程之间要靠同步机制建立。
    wenb1
        32
    wenb1   2018-12-05 09:09:22 +08:00
    感谢所有回答我问题的大佬,我继续去学习了!有问题我还会回来的。。。
    azhangbing
        33
    azhangbing   2018-12-05 09:33:41 +08:00   ♥ 1
    楼主可以看这个 http://www.iteye.com/topic/806990 这叫线程生产者消费者
    20015jjw
        34
    20015jjw   2018-12-05 09:59:34 +08:00 via Android
    看中文说这个根本看不懂。。。
    wenb1
        35
    wenb1   2018-12-05 10:14:17 +08:00
    @azhangbing 你说的很有道理,我去看看,多谢了
    wenb1
        36
    wenb1   2018-12-05 10:14:46 +08:00
    @20015jjw 我是中英文结合,书看英文的,视频看中文的
    dezhou9
        38
    dezhou9   2019-01-05 16:00:33 +08:00 via Android
    信号量和读写锁都不如 rcu 性能好
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1908 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 28ms · UTC 10:42 · PVG 18:42 · LAX 02:42 · JFK 05:42
    ♥ Do have faith in what you're doing.