繁体   English   中英

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

[英]Java synchronized method lock on object, or method?

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

例:

class X {

    private int a;
    private int b;

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

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

}

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

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

如果您希望一次仅同步一个变量,那么两个线程在访问不同变量时不会相互阻塞,您可以在synchronized ()块中单独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();
    }
}

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

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

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

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

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

来自同步方法的 “The Java™Tutorials”:

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

同步块上的“The Java™Tutorials”:

同步语句对于通过细粒度同步提高并发性也很有用。 例如,假设类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++;
        }
    }
}

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

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

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

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

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

来自oracle文档链接

使方法同步有两个影响:

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

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

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

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

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

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

 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。

这个例子(尽管不是很好)可以提供对锁定机制的更多了解。 如果incrementA同步的 ,并且incrementB 不同步 ,则incrementB将在ASAP执行,但如果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();
    }
}

是的,它会阻止其他方法,因为同步方法适用于作为指出整个类对象....但无论如何它会阻止ONLY,同时执行在任何方法或ADDA ADDB它进入,因为当它完成的总和其他线程的执行...一个线程将释放对象,另一个线程将访问另一个方法,等等完美工作。

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

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

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

也会工作

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM