214

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

例子:

class X {

    private int a;
    private int b;

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

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

}

x.addA(2个线程可以同时访问同一个X类实例x.addB()吗?

4

11 回答 11

221

如果您将该方法声明为已同步(正如您通过键入所做的那样),您将在整个public synchronized void addA()对象上同步,因此从同一对象访问不同变量的两个线程无论如何都会相互阻塞。

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

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();
    }
}
于 2010-06-15T17:48:33.363 回答
75

在方法声明上同步是语法糖:

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

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

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

我认为如果 Java 设计者当时知道现在对同步的理解,他们就不会添加语法糖,因为它往往会导致并发的错误实现。

于 2010-06-15T17:45:20.703 回答
26

来自关于同步方法的“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++;
        }
    }
}
于 2017-01-09T09:46:53.250 回答
14

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

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

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

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

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

于 2010-06-15T17:39:39.483 回答
7

来自 oracle 文档链接

使方法同步有两个效果:

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

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

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

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

于 2016-02-21T10:12:15.453 回答
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。

于 2015-08-07T10:57:35.827 回答
4

这个例子(虽然不是很漂亮)可以提供对锁定机制的更多见解。如果incrementA同步的,而incrementB不同步的,那么incrementB将尽快执行,但如果incrementB也是同步的,那么它必须“等待”让incrementA完成,然后incrementB才能完成它的工作。

这两种方法都被调用到单个实例对象上,在这个例子中它是:job,'竞争'线程是aThreadmain

尝试在incrementB中使用“ synchronized ” ,不使用它,您会看到不同的结果。如果incrementB也是“ synchronized ”,那么它必须等待incrementA () 完成。每个变体运行几次。

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();
    }
}
于 2016-12-09T16:52:17.257 回答
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++;
      }
   }
}
于 2010-06-15T18:05:10.490 回答
3

是的,它会阻塞另一个方法,因为同步方法适用于整个类对象,如所指出的......但无论如何它只会在它输入的任何方法 addA 或 addB 中执行求和时阻塞另一个线程执行,因为当它完成...一个线程将释放对象,另一个线程将访问另一个方法,依此类推。

我的意思是“同步”正是为了阻止另一个线程在执行特定代码时访问另一个线程。所以最后这段代码可以正常工作。

最后一点,如果有一个 'a' 和 'b' 变量,而不仅仅是一个唯一变量 'a' 或任何其他名称,则无需同步此方法,因为访问其他 var 是完全安全的(其他内存地点)。

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

也会起作用

于 2017-05-26T22:29:23.927 回答
1

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

于 2019-05-20T03:02:50.600 回答
0

这可能不起作用,因为从 Integer 到 int 的装箱和自动装箱取决于 JVM,如果两个不同的数字在 -128 和 127 之间,它们很可能会被散列到相同的地址。

于 2015-07-29T21:36:12.743 回答