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

发布于 2024-09-05 18:26:36 字数 374 浏览 14 评论 0原文

如果我在同一个类中有 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() 吗?

If I have 2 synchronized methods in the same class, but each accessing different variables, can 2 threads access those 2 methods at the same time? Does the lock occur on the object, or does it get as specific as the variables inside the synchronized method?

Example:

class X {

    private int a;
    private int b;

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

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

}

Can 2 threads access the same instance of class X performing x.addA() and x.addB() at the same time?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

帥小哥 2024-09-12 18:26:36

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

If you declare the method as synchronized (as you're doing by typing public synchronized void addA()) you synchronize on the whole object, so two threads accessing a different variable from the same object would block each other anyway.

If you want to synchronize only on one variable at a time, so two threads won't block each other while accessing different variables, you have synchronize on them separately in synchronized () blocks. If a and b were object references you would use:

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

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

But since they're primitives you can't do this.

I would suggest you to use AtomicInteger instead:

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

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

    public void addB(){ 
        b.incrementAndGet();
    }
}
狼亦尘 2024-09-12 18:26:36

Synchronized 在方法声明上是这样的语法糖:

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

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

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

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

Synchronized on the method declaration is syntactical sugar for this:

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

On a static method it is syntactical sugar for this:

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

I think if the Java designers knew then what is understood now about synchronization, they would not have added the syntactical sugar, as it more often than not leads to bad implementations of concurrency.

檐上三寸雪 2024-09-12 18:26:36

来自“Java™ 教程”中的同步方法

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

来自同步块的“Java™教程”:

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

(强调我的)

假设您有 2 个非交错变量。因此,您希望同时从不同的线程访问每一个。您需要不在对象类本身上定义,而是在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++;
        }
    }
}

From "The Java™ Tutorials" on synchronized methods:

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.

From "The Java™ Tutorials" on synchronized blocks:

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.

(Emphasis mine)

Suppose you have 2 non-interleaving variables. So you want to access to each one from a different threads at the same time. You need to define the lock not on the object class itself, but on the class Object like below (example from the second Oracle link):

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++;
        }
    }
}
晨与橙与城 2024-09-12 18:26:36

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

在方法中添加“同步”意味着运行代码的线程必须先获取对象的锁,然后才能继续。添加“静态同步”意味着运行代码的线程必须获取类对象上的锁才能继续。或者,您可以将代码包装在如下所示的块中:

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

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

您可以选择:

The lock accessed is on the object, not on the method. Which variables are accessed within the method is irrelevant.

Adding "synchronized" to the method means the thread running the code must acquire the lock on the object before proceeding. Adding "static synchronized" means the thread running the code must acquire the lock on the class object before proceeding. Alternatively you can wrap code in a block like this:

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

so that you can specify the object whose lock must be acquired.

If you want to avoid locking on the containing object you can choose between:

°如果伤别离去 2024-09-12 18:26:36

来自oracle文档链接

使方法同步有两个效果:

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

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

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

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

From oracle documentation link

Making methods synchronized has two effects:

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

Have a look at this documentation page to understand intrinsic locks and lock behavior.

This will answer your question: On same object x , you can't call x.addA() and x.addB() at same time when one of the synchronized methods execution is in progress.

梦中楼上月下 2024-09-12 18:26:36

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

 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。

If you have some methods which are not synchronized and are accessing and changing the instance variables. In your example:

 private int a;
 private int b;

any number of threads can access these non synchronized methods at the same time when other thread is in the synchronized method of the same object and can make changes to instance variables.
For e.g :-

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

You need to avoid the scenario that non synchronized methods are accessing the instance variables and changing it otherwise there is no point of using synchronized methods.

In the below scenario:-

class X {

        private int a;
        private int b;

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

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

Only one of the threads can be either in addA or addB method but at the same time any number of threads can enter changeState method. No two threads can enter addA and addB at same time(because of Object level locking) but at same time any number of threads can enter changeState.

梦巷 2024-09-12 18:26:36

这个例子(虽然不是很好)可以让我们更深入地了解锁定机制。如果incrementA已同步,而incrementB未同步,则incrementB将为尽快执行,但如果 incrementB同步,那么它必须在 incrementB 之前“等待”incrementA 完成> 可以完成其工作。

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

尝试在 incrementB 中使用“同步”,如果不使用它,您将看到不同的结果。如果 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();
    }
}

This example (although not pretty one) can provide more insight into locking mechanism. If incrementA is synchronized, and incrementB is not synchronized, then incrementB will be executed ASAP, but if incrementB is also synchronized then it has to 'wait' for incrementA to finish, before incrementB can do its job.

Both methods are called onto single instance - object, in this example it is: job, and 'competing' threads are aThread and main.

Try with 'synchronized' in incrementB and without it and you will see different results.If incrementB is 'synchronized' as well then it has to wait for incrementA() to finish. Run several times each variant.

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();
    }
}
葬﹪忆之殇 2024-09-12 18:26:36

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

You can do something like the following. In this case you are using the lock on a and b to synchronized instead of the lock on "this". We cannot use int because primitive values don't have locks, so we use Integer.

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}
逆光下的微笑 2024-09-12 18:26:36

是的,它会阻止其他方法,因为同步方法适用于所指出的整个类对象......但无论如何,它只会在执行以下操作时阻止其他线程执行sum 在它进入的任何方法 addA 或 addB 中,因为当它完成时......一个线程将释放对象,另一个线程将访问另一个方法,依此类推,完美工作。

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

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

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

也会发挥作用

Yes, it will block the other method because synchronized method applies to the WHOLE class object as pointed .... but anyway it will block the other thread execution ONLY while performing the sum in whatever method addA or addB it enters, because when it finish ... the one thread will FREE the object and the other thread will access the other method and so on perfectly working.

I mean the "synchronized" is made precisely for blocking the other thread from accessing another while in a specific code execution. SO FINALLY THIS CODE WILL WORK FINE.

As a final note, if there is an 'a' and 'b' variables, not just an unique variable 'a' or whatever other name, there is no need to synchronize this methods cause it is perfectly safe accesing other var (Other memory location).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

Will work as well

野却迷人 2024-09-12 18:26:36

在Java同步中,如果一个线程想要进入synchronized方法/块,它将获取以下锁:

  • 此对象<的所有同步非静态方法。 /strong>
    不仅在一个同步方法上,线程还使用
  • 此类的所有同步静态方法,

因此线程将调用同步方法addA() 将获取 addA()addB() 上的锁,因为两者都是同步的。
因此具有相同对象的其他线程无法执行addB()。

In Java synchronization,if a thread want to enter into synchronized method/block, it will acquire the lock on:

  • all synchronized non-static methods of this object
    not just on one synchronized method that thread is using
  • all synchronized static methods of this class as well

So the thread which will call the synchronized method addA() for example, will acquire a lock on addA() and addB() as both are synchronized.
So the other threads with same object cannot execute addB().

魄砕の薆 2024-09-12 18:26:36

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

This might not work as the boxing and autoboxing from Integer to int and viceversa is dependant on JVM and there is high possibility that two different numbers might get hashed to same address if they are between -128 and 127.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文