如果我在同一个类中有2个同步方法,但是每个方法访问不同的变量,2个线程可以同时访问这2个方法吗?锁是发生在对象上,还是与同步方法中的变量一样特定?

例子:

class X {

    private int a;
    private int b;

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

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

}

两个线程可以同时访问类X的同一个实例,执行x.a da()和x.a bbb()吗?


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

在方法中添加"synchronized"意味着运行代码的线程必须在继续之前获得对象上的锁。添加“static synchronized”意味着运行代码的线程必须在继续之前获取类对象上的锁。或者,你也可以像这样将代码封装在一个块中:

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

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

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

使用指定不同锁的同步块 使a和b具有原子性(使用java.util.concurrent.atomic)


Synchronized在方法声明上是语法糖:

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

在静态方法中,它是这样的语法糖:

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

我认为,如果Java设计人员当时就知道现在对同步的理解,他们就不会添加语法糖,因为它通常会导致糟糕的并发实现。


如果你将方法声明为synchronized(就像你通过输入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();
    }
}

你可以像下面这样做。在这种情况下,您使用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++;
      }
   }
}

这可能行不通,因为从Integer到int的装箱和自动装箱依赖于JVM,如果两个不同的数字在-128和127之间,很可能会被散列到相同的地址。


如果你有一些方法没有同步,并且正在访问和改变实例变量。在你的例子中:

 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。


从oracle文档链接

使方法同步有两个效果:

First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object. Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads

看看这个文档页,了解内在锁和锁的行为。

这将回答您的问题:在同一个对象x上,当其中一个同步方法正在执行时,您不能同时调用x.a da()和x.a dbb()。


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

这两个方法都被调用到单个实例对象上,在这个例子中是:job,而“竞争”线程是aThread和main。

尝试在incrementB中使用'synchronized'和不使用它,你会看到不同的结果。如果incrementB也是“同步”的,那么它必须等待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();
    }
}

关于同步方法的“Java™教程”:

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

关于同步块的“Java™教程”:

Synchronized statements are also useful for improving concurrency with fine-grained synchronization. Suppose, for example, class MsLunch has two instance fields, c1 and c2, that are never used together. All updates of these fields must be synchronized, but there's no reason to prevent an update of c1 from being interleaved with an update of c2 — and doing so reduces concurrency by creating unnecessary blocking. Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks.

(强调我的)

假设你有两个不交叉的变量。所以你想同时从不同的线程访问每一个。你不需要在对象类本身上定义锁,而是在类object上定义锁,如下所示(示例来自第二个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++;
        }
    }
}

是的,它将阻塞其他方法,因为synchronized方法应用于指向....的WHOLE类对象但无论如何,它只会在addA或addB方法中执行求和时阻塞其他线程的执行,因为当它完成…一个线程将释放对象,另一个线程将访问另一个方法,以此类推。

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

作为最后的注意,如果有一个'a'和'b'变量,而不仅仅是一个唯一的变量'a'或任何其他名称,没有必要同步这个方法,因为它是完全安全的访问其他var(其他内存位置)。

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

也会起作用


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