Java同步方法锁定对象或方法?

时间:2010-06-15 17:38:08

标签: java multithreading thread-safety locking synchronized

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

示例:

class X {

    private int a;
    private int b;

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

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

}

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

11 个答案:

答案 0 :(得分:168)

如果您将方法声明为同步(正如您通过键入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();
    }
}

答案 1 :(得分:58)

对方法声明进行同步是这样的语法糖:

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

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

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

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

答案 2 :(得分:13)

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

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

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

以便您可以指定必须获取其锁定的对象。

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

答案 3 :(得分:13)

From the Java SE essentials on synchronized methods

  

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

来自Java SE essentials on synchronized blocks

  

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

(强调我的。)

你有两个变量no-interleaved。因此,您希望同时访问来自不同线程的每个线程。你需要定义锁不在对象类本身上,而是在类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++;
        }
    }
}

答案 4 :(得分:4)

来自oracle文档link

使方法同步有两个影响:

  

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

     

其次,当synchronized方法退出时,它会自动与同一对象的同步方法的任何后续调用建立一个before-before关系。这可以保证对所有线程都可以看到对象状态的更改

查看本文档page以了解内部锁和锁定行为。

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

答案 5 :(得分:2)

您可以执行以下操作。在这种情况下,您使用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++;
      }
   }
}

答案 6 :(得分:2)

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

 private int a;
 private int b;

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

 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。

答案 7 :(得分:1)

这个例子(尽管不是很好)可以提供对锁定机制的更多了解。如果 incrementA 同步,并且 incrementB 未同步,那么 incrementB 将是尽快执行,但如果 incrementB 同步那么它必须'等待' incrementA 完成,然后 incrementB >可以做好自己的工作。

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

尝试 incrementB 中的“同步”,如果没有它,您会看到不同的结果。如果 incrementB 是“同步< / strong>'然后它必须等待 incrementA ()才能完成。每个变体运行几次。

200dp

答案 8 :(得分:1)

是的,它会阻止另一个方法,因为synchronized方法适用于指向的 WHOLE 类对象....但无论如何它会阻止其他线程执行 ONLY 在它进入的任何方法addA或addB中执行求和时,因为当它完成时...一个线程将自由对象,另一个线程将访问另一个方法,等等完美地工作。 / p>

我的意思是“同步”正是为了阻止其他线程在特定代码执行时访问另一个线程。所以这个代码最终会很精细。

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

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

也会起作用

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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