Java同步方法锁定对象还是方法?


190

如果我在同一个类中有2个同步方法,但是每个方法都访问不同的变量,那么2个线程可以同时访问这2个方法吗?锁是否发生在对象上,或者是否与同步方法中的变量一样具体?

例:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

2个线程可以访问类X执行相同的实例x.addA(),并x.addB()在同一时间?

Answers:


196

如果将方法声明为已同步(就像通过键入所做的那样public synchronized void addA()),则会在整个对象上进行同步,因此,从同一对象访问不同变量的两个线程将始终相互阻塞。

如果您一次只想同步一个变量,那么两个线程在访问不同的变量时不会互相阻塞,您可以分别在synchronized ()块中同步它们。如果ab是对象引用,则可以使用:

public void addA() {
    synchronized( a ) {
        a++;
    }
}

public void addB() {
    synchronized( b ) {
        b++;
    }
}

但是由于它们是原始类型,所以您不能这样做。

我建议您改用AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}

181
如果在该方法上同步,则锁定整个对象,因此,两个线程访问与此对象不同的变量将始终相互阻塞。 这有点误导。在方法上进行同步在功能上等同于在方法synchronized (this)主体周围具有一个块。对象“ this”不会被锁定,而是将对象“ this”用作互斥对象,并防止主体与也与此“ this”同步的其他代码段同时执行。它对未同步的“ this”的其他字段/方法没有影响。
Mark Peters 2010年

13
是的,这确实是一种误导。对于真实示例-查看此内容-stackoverflow.com/questions/14447095/…-摘要:锁定仅在同步方法级别,并且对象的实例变量可以由其他线程访问
mac

5
第一个例子从根本上被打破了。如果ab是对象(例如)Integer,则在应用运算符时,您正在用不同的对象替换实例++
Holger

解决您的答案并初始化AtomicInteger:AtomicInteger a = new AtomicInteger(0);
Mehdi

也许应该用关于同步对象本身的另一本解释中的说明来更新此anwser:stackoverflow.com/a/10324280/1099452
lucasvc

70

为此,在方法声明上同步的是语法糖:

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

在静态方法上,它是语法糖:

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

我认为,如果Java设计人员知道现在对同步有什么了解,他们就不会添加语法糖,因为它经常会导致并发的不良实现。


3
不对。同步方法生成的字节码与synced(object)不同。虽然功能等效,但它不仅仅是语法糖。
郭富城2010年

10
我不认为“语法糖”严格定义为等效的字节码。关键是它在功能上是等效的。
Yishai 2010年

1
如果Java设计师们知道发生了什么已经知道关于他们将有监视器/应该做不同的它,而不是基本的Unix仿真的内部结构。Per Brinch Hansen在看到Java并发原语时说,“显然我是徒劳的”
2013年

这是真的。OP给出的示例似乎锁定了每个方法,但实际上它们都锁定在同一对象上。极具欺骗性的语法。在使用Java 10年以上之后,我不知道这一点。因此,出于这个原因,我将避免使用同步方法。我一直认为为同步定义的每个方法都创建一个不可见的对象。
彼得·奎林

20

从“ Java™教程”中的同步方法开始

首先,不可能对同一对象的两次同步方法调用进行交织。当一个线程正在执行对象的同步方法时,所有其他线程调用同一对象块的同步方法(挂起执行),直到第一个线程对该对象完成。

同步块上的“ Java™教程”中:

同步语句对于通过细粒度同步提高并发性也很有用。例如,假设类MsLunch有两个实例字段c1和c2,它们从未一起使用。这些字段的所有更新都必须同步,但是没有理由阻止c1更新与c2更新交织 -这样做会通过创建不必要的阻塞来减少并发性。代替使用同步方法或以其他方式使用与此关联的锁,我们仅创建两个对象来提供锁。

(强调我的)

假设您有2个非交织变量。因此,您希望同时从不同的线程访问每个线程。您需要定义不上对象类本身,而是对类对象像下面(例如,从第二个Oracle链接):

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

14

访问的锁在对象上,而不在方法上。在该方法中访问哪些变量无关紧要。

在该方法中添加“已同步”意味着运行代码的线程必须在继续操作之前获取该对象的锁。添加“静态同步”意味着运行代码的线程必须在继续之前获得对类对象的锁定。或者,您可以将代码包装在这样的块中:

public void addA() {
    synchronized(this) {
        a++;
    }
}

这样您就可以指定必须获取其锁的对象。

如果要避免锁定包含对象,则可以选择:


7

来自oracle文档链接

使方法同步具有两个作用:

首先,不可能对同一对象的两次同步方法调用进行交织。当一个线程正在执行对象的同步方法时,所有其他线程调用同一对象块的同步方法(挂起执行),直到第一个线程对该对象完成。

其次,当同步方法退出时,它会与随后对同一对象的同步方法的任何调用自动建立先发生关系。这保证了对象状态的更改对所有线程都是可见的

请查看此文档页面以了解固有锁和锁行为。

这将回答您的问题:在同一对象x上,当其中一个同步方法执行正在进行时,您无法同时调用x.addA()和x.addB()。


4

如果您有一些方法不同步,并且正在访问和更改实例变量。在您的示例中:

 private int a;
 private int b;

当其他线程处于同一对象的同步方法中时,任意数量的线程可以同时访问这些非同步方法,并且可以更改实例变量。例如:

 public void changeState() {
      a++;
      b++;
    }

您需要避免非同步方法正在访问实例变量并对其进行更改的情况,否则就没有使用同步方法的意义。

在以下情况下:

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

只有一个线程可以处于addA或addB方法中,但同时任何数量的线程都可以进入changeState方法。没有两个线程可以同时输入addA和addB(由于对象级别锁定),但是同时有任意数量的线程可以输入changeState。


3

您可以执行以下操作。在这种情况下,您将使用a和b上的锁进行同步,而不是使用“ this”上的锁。我们不能使用int因为原始值没有锁,所以我们使用Integer。

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

是的,它将阻止另一个方法,因为同步方法将按指向的方式应用于WHOLE类对象。但是无论如何,它将在执行所输入的任何方法addA或addB的总和时阻止其他线程执行,因为完成时...一个线程将释放对象,而另一个线程将访问另一种方法,以此类推。

我的意思是“同步”是专门为阻止另一个线程在特定代码执行中访问另一个线程而制作的。最终,此代码将很有效。

最后要注意的是,如果有一个'a'和'b'变量,而不仅仅是一个唯一变量'a'或其他名称,则无需同步此方法,因为它可以安全地访问其他var(其他内存)位置)。

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

也会工作


2

这个示例(尽管不是一个很好的示例)可以提供有关锁定机制的更多信息。如果增量型同步的,并incrementB不同步的,那么incrementB会尽快执行,但如果incrementB同步那么它必须“等待”的增量型到结束,前incrementB可以完成自己的工作。

这两个方法都被调用到单个实例对象上,在本例中为:job和“竞争”线程分别是aThreadmain

如果在没有增量的情况下尝试在增量B中使用“已同步 ”,则会看到不同的结果。如果增量B也已“ 同步 ”,则必须等待增量A()完成。每个变体运行几次。

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}

1

在Java同步中,如果一个线程想要进入同步方法,它将获取该对象的所有同步方法的锁,而不仅仅是该线程正在使用的一个同步方法。因此执行addA()的线程将获得对addA()和addB()的锁定,因为这两个线程都是同步的。因此具有相同对象的其他线程无法执行addB()。


0

这可能不起作用,因为从Integer到int的装箱和自动装箱取决于JVM,反之亦然,这取决于JVM,如果两个不同的数字介于-128和127之间,则很可能会将两个不同的数字散列到同一地址。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.