首页 文章

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

提问于
浏览
150

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

10 回答

  • 1

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

  • 12

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

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

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

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

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

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

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

  • 1

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

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

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

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

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

  • 154

    From the Java SE essentials on synchronized methods

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

    来自Java SE essentials on synchronized blocks

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

    (强调我的 . )

    你有2个变量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++;
            }
        }
    }
    
  • 2

    来自oracle文档link

    使方法同步有两个影响:

    首先,对同一对象的两个同步方法的调用不可能进行交错 . 当一个线程正在为对象执行同步方法时,所有其他线程调用同一对象的同步方法(暂停执行)直到第一个线程完成对象 . 其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用 Build 先发生关系 . 这可以保证对所有线程都可以看到对象状态的更改

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

    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.

  • 0

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

  • 5

    这个例子(尽管不是很好)可以提供对锁定机制的更多了解 . 如果incrementA是 synchronized ,incrementB是 not synchronized ,则incrementB将尽快执行,但如果incrementB也是 synchronized ,则在incrementB完成其工作之前,它必须为incrementA完成'wait' .

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

    尝试使用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();
        }
    }
    
  • 54

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

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

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

    class X {
    
    private int a;
    private int b;
    
    public void addA(){
        a++;
    }
    
    public void addB(){
        b++;
    }}
    

    也会工作

相关问题