关于同步方法、锁和监视器的说明

Clarifications about synchronized methods, locks and monitor

试图加深我在 Java 中对同步的了解,我的困惑从以下语句开始,摘自 here

Each object in Java is associated with a monitor, which a thread can lock or unlock. [...] If execution of the [synchronized] method's body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor.

所以,我在想象一个像圆顶一样的监视器,覆盖整个对象,并防止两个线程同时访问它。更清楚地说,我认为,给定

class MySync{
    synchronized void foo(){}
    void bar(){}
    String x;
}

如果 threadA 访问 运行(同步)foo() 方法 5 秒,它 "activates the dome" 阻止 threadB 访问对象的任何其他成员,如 barx.

我写这几行代码只是为了做一些测试,后来我发现我错了...

public class Main{

    public static void main(String args[]) throws InterruptedException{
        new Main();
    }

    MySync ms = new MySync();               
    RunnableA r1=new RunnableA(ms);
    RunnableB r2=new RunnableB(ms);

    Main() throws InterruptedException{
        r1.start();
        Thread.sleep(1000);
        r2.start();
    }

}

class MySync{

    synchronized void foo(String tab){

        System.out.println(tab+Thread.currentThread().getName()+" in foo()");

        if("A".equals(Thread.currentThread().getName())){
            System.out.println(tab+Thread.currentThread().getName()+" Waiting 5 seconds");
            try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
        }

        System.out.println(tab+Thread.currentThread().getName()+" out foo()");

    }

    void bar(String tab){
        System.out.println(tab+Thread.currentThread().getName()+" in bar()");
        System.out.println(tab+Thread.currentThread().getName()+" out bar()");
    }

}

class RunnableA implements Runnable{

    String tab="";
    Thread t=new Thread(this);
    MySync ms;

    RunnableA(MySync ms){
        this.ms=ms;
        t.setName("A");
    }

    void start(){t.start();}

    @Override
    public void run(){
        System.out.println(tab+"A Running ");
        ms.foo(tab);
        System.out.println(tab+"A End running");
    }
}

class RunnableB implements Runnable{

    String tab="    ";
    Thread t=new Thread(this);
    MySync ms;

    RunnableB(MySync ms){
        this.ms=ms;
        t.setName("B");
    }

    void start(){t.start();}

    @Override
    public void run(){
        System.out.println(tab+"B Running ");
        ms.bar(tab);
        System.out.println(tab+"B End running");
    }
}

这是输出:

A Running 
A in foo()
A Waiting 5 seconds    //threadA stuck in syncronized foo() method
    B Running          //threadB accesses bar() method anyway
    B in bar()
    B out bar()
    B End running
A out foo()
A End running

我的问题是:

Java 中的每个对象都与一个监视器 关联是什么意思?是不是有点暧昧?如果我也声明 synchronized bar(),我会得到我期望的行为。这是否意味着我想象中的圆顶一次只覆盖所有同步方法(即所有同步方法的一把锁)?

是的,假想的圆顶仅涵盖该对象的所有同步方法以及在该对象上显式同步的代码片段,例如:

,,,
synchronize(this) {
doSomething();
}

并非所有代码部分都是线程安全敏感的,因此某些方法可以'unsynchronized'在同一个对象中,这样可以加快执行速度。

"What does it mean that each object in Java is associated with a monitor?".

这意味着您在每个对象上都有可用的监视器。您可以通过在对象上声明 synchronized 方法或使用 synchronize(obj) 方法来访问它。所以你不需要引入另一个提供监控功能的'entity'。 至于适当的监视器,您可以访问每个对象上的 wait() 和 notify() 方法。

syncrhonize(obj) {
 while(someConditionIsNotTrue) {
 obj.wait();
 }
}

因此,只要您的代码逻辑需要独占访问该对象,您就可以使用内置监视器并对其调用同步。

为了提供线程安全,所有部分都必须按照相同的规则运行,并在需要时使用synchronized。为了防止由于遗漏而导致的错误,有更高级别的并发机制,如 AtomicInteger 和 ConcurrentCollections。

同步是一种开发人员工具,用于在线程之间启用对资源的协调访问。

Java Language Specification 状态

A synchronized statement acquires a mutual-exclusion lock (§17.1) on behalf of the executing thread, executes a block, then releases the lock. While the executing thread owns the lock, no other thread may acquire the lock.

在您的情况下,线程 A 获取锁并执行 foo 已实现以要求锁。然后线程 B 执行 bar 尚未实现以要求锁定。 B 因此未被阻止。

再次来自规范

Acquiring the lock associated with an object does not in itself prevent other threads from accessing fields of the object or invoking un-synchronized methods on the object. Other threads can also use synchronized methods or the synchronized statement in a conventional manner to achieve mutual exclusion.

将同步视为各方之间的协议,即访问某些内容 必须通过同步目标完成。如果某些代码忽略这个约定,直接访问那个something,可能会导致Java tutorials on Synchronization.

中提到的线程干扰和内存一致性错误。

为了处理这个圆顶机制,每个 Java 对象都存在一个 lock flag 类型的标志,并且使用 synchronized 允许根据以下模式与该锁定标志进行交互.

调用同步 (this) 后,锁标志将由线程持有,直到它完成处理或经历某些特定的中断情况。

当第一个线程获取到锁标志时,如果第二个线程来并尝试访问 this引用的对象,它不会被能够,因为没有锁定标志。因此,线程调度程序会将新线程放入等待对象锁定标志的线程池中。它会一直呆在那里直到释放锁标志,并且可能会立即访问对象,也可能不会立即访问该对象,具体取决于线程调度程序的调度决定

锁定标志将在以下事件发生时释放

  • 线程通过同步代码块的结尾
  • 当同步代码块抛出中断、return或异常时

为了正确使用同步,始终确保

非常重要
  • 所有对敏感数据的访问都是synchronized(你应该这样做)
  • 所有被synchronized保护的敏感数据必须是私有的