【问题标题】:If I synchronized two methods on the same class, can they run simultaneously?如果我在同一个类上同步了两个方法,它们可以同时运行吗?
【发布时间】:2013-03-04 12:58:36
【问题描述】:

如果我在同一个类上同步了两个方法,它们可以同时在同一个对象上运行吗?例如:

class A {
    public synchronized void methodA() {
        //method A
    }

    public synchronized void methodB() {
        // method B
    }
}

我知道我不能在两个不同线程中对同一个对象运行两次methodA()methodB() 中的相同内容。

但是我可以在 methodA() 仍在运行时在不同的线程上运行 methodB() 吗? (同一个对象)

【问题讨论】:

    标签: java multithreading synchronized java-threads


    【解决方案1】:

    这两种方法都会锁定同一个监视器。因此,您不能同时在不同线程的同一个对象上执行它们(两种方法中的一种会阻塞,直到另一种完成)。

    【讨论】:

    • 我对这个问题有一个补充。假设这两个方法都是静态的,现在使用 Class 调用 methodA,而使用 t1 中的 A.methodA() 和 t2 中的 obj.methodB() 之类的对象调用 methodB。现在会发生什么,他们会阻止吗????
    • @amod0017: 当methodB()static 时,obj.methodB()A.methodB() 的同义词。因此,是的,它们会阻塞(在类的而不是对象的监视器上)。
    • 将尝试恢复。 :)
    • @NPE 因此,即使两个方法都是静态的,并且同一对象上的 2 个线程 t1 和 t2 尝试同时调用 methodA() 和 methodB() 那么只有 1 个(比如 t1)线程将执行,另一个线程必须等到 t1 释放锁?
    • 请记住,静态方法对.class 对象使用锁定。所以如果你有class A {static synchronized void m() {} }。然后一个线程调用new A().m() 它获取new A() 对象的锁定。如果然后另一个线程调用A.m(),它输入方法没有问题,因为它寻找的是锁定A.class 对象,而没有线程拥有这种锁。因此,即使您声明了方法synchronized,它实际上被两个不同的线程在同一时间访问。因此:永远不要使用对象引用来调用静态方法
    【解决方案2】:

    在示例中 methodA 和 methodB 是实例方法(与静态方法相反)。将synchronized 放在实例方法上意味着线程必须在调用该方法的对象实例上获取锁(“内在锁”),然后线程才能开始执行该方法中的任何代码。

    如果您有两个不同的实例方法标记为同步,并且不同的线程在同一个对象上同时调用这些方法,那么这些线程将竞争同一个锁。一旦一个线程获得锁,所有其他线程都将被该对象上的所有同步实例方法关闭。

    为了让这两种方法同时运行,它们必须使用不同的锁,如下所示:

    class A {
        private final Object lockA = new Object();
        private final Object lockB = new Object();
    
        public void methodA() {
            synchronized(lockA) {
                //method A
            }
        }
    
        public void methodB() {
            synchronized(lockB) {
                //method B
            }
        }
    }
    

    同步块语法允许指定执行线程需要获取内在锁定才能进入块的特定对象。

    要理解的重要一点是,即使我们在单个方法上放置了“同步”关键字,核心概念是幕后的内在锁。

    the Java tutorial 是这样描述这种关系的:

    同步是围绕称为内部锁或监视器锁的内部实体构建的。 (API 规范通常将此实体简称为“监视器”。)内在锁在同步的两个方面都发挥作用:强制对对象状态的独占访问和建立对可见性至关重要的先发生关系。

    每个对象都有一个与之关联的内在锁。按照惯例,需要对对象字段进行排他和一致访问的线程必须在访问对象之前获取对象的内在锁,然后在完成访问时释放内在锁。在获得锁和释放锁之间,线程被称为拥有内在锁。只要一个线程拥有一个内在锁,其他线程就不能获得相同的锁。其他线程在尝试获取锁时会阻塞。

    锁定的目的是保护共享数据。仅当每个锁保护不同的数据成员时,您才可以使用上面示例代码中所示的单独锁。

    【讨论】:

    • 所以在这个例子中,锁是在 lockA\lockB 对象上而不是在类 A 上?这是一个类级锁定的例子吗?
    • @Nimrod:它锁定了 lockA 和 lockB 对象,而不是 A 的实例。这里没有锁定一个类。类级锁定意味着使用 static synchronizedsynchronized (A.class) 之类的方式获取类对象上的锁定
    • 这里是 link 的 java 教程,解释了这里的答案。
    【解决方案3】:

    Java线程在进入实例同步java方法并获取类级锁对象级锁 /strong> 进入静态同步java方法时。

    在您的情况下,方法(实例)属于同一类。因此,当一个线程进入 java 同步方法或阻塞时,它会获取一个锁(调用该方法的对象)。所以在第一个方法完成并释放lock(on object)之前,不能在同一个对象上同时调用其他方法。

    【讨论】:

    • 如果我在类的两个不同实例上有两个线程,那么它们将能够同时执行这两种方法,这样一个线程调用一个同步方法,另一个调用第二个同步方法。如果我的理解是正确的,那么我可以使用 private final Object lock = new object(); with synchronized 来允许只有一个线程能够执行其中一种方法吗?谢谢
    【解决方案4】:

    在您的情况下,您在同一个类实例上同步了两个方法。所以,这两个方法不能同时运行在同一个A类实例的不同线程上,但是可以运行在不同的A类实例上。

    class A {
        public synchronized void methodA() {
            //method A
        }
    }
    

    等同于:

    class A {
        public void methodA() {
            synchronized(this){
                // code of method A
            }
        }
    }
    

    【讨论】:

    • 如果我将锁定义为private final Object lock = new Object();,现在在两种方法中将lock 与同步块一起使用,那么您的说法是否成立? IMO 因为 Object 是所有对象的父类,所以即使线程在类的不同实例上,一次也只有一个可以访问同步块内的代码。谢谢。
    • 如果在类中定义“private final Object lock”并与之同步,则为每个类实例填充锁,因此它的行为与 synchronized(this) 相同。
    • 是的,Object 是所有类的父类,但在您的情况下,“锁定”实例是“每个拥有类的实例”,因此它与“this”的同步效果相同。跨度>
    【解决方案5】:

    把你的代码想象成下面这样:

    class A {
    
    public void methodA() {
        synchronized(this){        
          //method A body
        }
    }
    
    public void methodB() {
        synchronized(this){
          // method B body
        }
    }
    

    所以,方法级别的同步只是意味着同步(this)。 如果任何线程运行了这个类的方法,它会在开始执行之前获得锁,并持有它直到方法执行完成。

    但是我可以在 methodA() 还在的时候在不同的线程上运行 methodB() 跑步? (同一个对象)

    确实不可能!

    因此,多个线程将无法同时在同一个对象上运行任意数量的同步方法。

    【讨论】:

    • 如果我在同一类的两个不同对象上创建线程会怎样?在这种情况下,如果我从一个线程调用一个方法,从第二个线程调用另一个方法,它们不会同时执行吗?
    • 它们会因为它们是不同的对象。也就是说,如果你想防止它,你可以使用静态方法并同步类或使用类变量对象作为锁或使类Singleton。 @Yug Singh
    【解决方案6】:

    来自 oracle 文档link

    使方法同步有两个效果:

    首先,同一对象上的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。

    其次,当一个同步方法退出时,它会自动与任何后续对同一对象的同步方法调用建立起之前的关系。这保证了对象状态的变化对所有线程都是可见的

    这将回答您的问题:在同一个对象上,当第一个同步方法执行正在进行时,您不能调用第二个同步方法。

    查看此文档page 以了解内在锁和锁定行为。

    【讨论】:

      【解决方案7】:

      为了清楚起见,静态同步和非静态同步方法可能同时或并发运行,因为一个具有对象级锁和其他类级锁。

      【讨论】:

        【解决方案8】:

        同步的 key 想法不容易陷入,它只有在 same object 实例上调用方法时才会生效 - 它已经被突出显示在答案和 cmets -

        下面的示例程序是为了清楚地指出相同的 -

        public class Test {
        
        public synchronized void methodA(String currentObjectName) throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA in");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodA out");
        }
        
        public synchronized void methodB(String currentObjectName)  throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB in");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "->" +currentObjectName + "->methodB out");
        }
        
        public static void main(String[] args){
            Test object1 = new Test();
            Test object2 = new Test();
            //passing object instances to the runnable to make calls later
            TestRunner runner = new TestRunner(object1,object2);
            // you need to start atleast two threads to properly see the behaviour
            Thread thread1 = new Thread(runner);
            thread1.start();
            Thread thread2 = new Thread(runner);
            thread2.start();
        }
        }
        
        class TestRunner implements Runnable {
        Test object1;
        Test object2;
        
        public TestRunner(Test h1,Test h2) {
            this.object1 = h1;
            this.object2 = h2;
        }
        
        @Override
        public void run() {
            synchronizedEffectiveAsMethodsCalledOnSameObject(object1);
            //noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(object1,object2);
        }
        
        // this method calls the method A and B with same object instance object1 hence simultaneous NOT possible
        private void synchronizedEffectiveAsMethodsCalledOnSameObject(Test object1) {
            try {
                object1.methodA("object1");
                object1.methodB("object1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // this method calls the method A and B with different object instances object1 and object2 hence simultaneous IS possible
        private void noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects(Test object1,Test object2) {
            try {
                object1.methodA("object1");
                object2.methodB("object2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
        

        如果在不同的对象实例上调用方法,请注意按预期允许同时访问的输出差异。

        输出 noEffectOfSynchronizedAsMethodsCalledOnDifferentObjects() commented - 输出的顺序是 methodA in > methodA Out .. methodB in > methodB Out

        和输出与 synchronizedEffectiveAsMethodsCalledOnSameObject() 评论 - 输出显示在突出显示的部分中 Thread1 和 Thread0 同时访问 methodA -

        增加线程数会使其更加明显。

        【讨论】:

          【解决方案9】:

          不,这是不可能的,如果有可能,那么两种方法都可能同时更新同一个变量,这很容易损坏数据。

          【讨论】:

            【解决方案10】:

            您是在对象上而不是在类上同步它。所以它们不能同时在同一个对象上运行

            【讨论】:

              【解决方案11】:

              是的,它们可以同时运行两个线程。如果您创建该类的 2 个对象,因为每个对象仅包含一个锁,并且每个同步方法都需要锁。 因此,如果您想同时运行,请创建两个对象,然后尝试使用这些对象引用来运行。

              【讨论】:

              • 我很确定他们的意思是如果在同一个对象上调用这两种方法
              【解决方案12】:

              两个不同的线程在同一个对象上执行一个共同的同步方法,由于对象是相同的,当一个线程使用同步方法时,它必须验证锁,如果启用了锁,这个线程会去等待状态,如果锁被禁用,那么它可以访问对象,当它访问时,它会启用锁并释放锁 只有当它的执行完成时。 当另一个线程到达时,它将验证锁,因为它已启用,它将等待第一个线程完成执行并释放对象上的锁,一旦锁被释放,第二个线程将获得对该对象的访问权并且它将启用锁,直到它被执行。 所以执行不会不是并发的,两个线程会一个一个地执行,当两个线程对不同的对象使用同步方法时,它们会并发运行。

              【讨论】:

              • 请正确标点和大写这个混乱。没有“varify”这样的词。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-06-30
              • 1970-01-01
              相关资源
              最近更新 更多