Java วิธีการทำข้อมูลให้ตรงกันล็อคกับวัตถุหรือวิธี?


191

ถ้าฉันมีวิธีการซิงโครไนส์ 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()ในเวลาเดียวกันได้หรือไม่

คำตอบ:


199

หากคุณประกาศวิธีการซิงโครไนซ์ (เช่นที่คุณทำโดยการพิมพ์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();
    }
}

181
หากคุณซิงโครไนซ์กับวิธีการที่คุณล็อควัตถุทั้งหมดดังนั้นสองเธรดที่เข้าถึงตัวแปรที่แตกต่างจากวัตถุเดียวกันนี้จะบล็อกซึ่งกันและกัน นั่นเป็นสิ่งที่ทำให้เข้าใจผิดเล็กน้อย การซิงโครไนซ์กับวิธีการนั้นเทียบเท่ากับการมีsynchronized (this)บล็อกอยู่รอบตัวของวิธีการ วัตถุ "นี้" ไม่ได้ล็อค แต่จะใช้วัตถุ "this" เป็น mutex และเนื้อความถูกป้องกันไม่ให้ทำงานพร้อมกันกับส่วนรหัสอื่น ๆ ที่ซิงโครไนซ์กับ "this" ไม่มีผลกับฟิลด์ / วิธีอื่นของ "this" ที่ไม่ได้ซิงโครไนซ์
Mark Peters

13
ใช่มันทำให้เข้าใจผิดอย่างแท้จริง สำหรับตัวอย่างจริง - ดูที่นี่ - stackoverflow.com/questions/14447095/… - สรุป: การล็อกอยู่ที่ระดับเมธอดที่ซิงโครไนซ์เท่านั้นและตัวแปรอินสแตนซ์ของวัตถุสามารถเข้าถึงได้โดยเธรดอื่น ๆ
mac

5
ตัวอย่างแรกแตกหักโดยพื้นฐาน ถ้าaและbเป็นวัตถุเช่นIntegers คุณกำลังซิงโครไนซ์กับอินสแตนซ์ที่คุณกำลังแทนที่ด้วยวัตถุอื่นเมื่อใช้++โอเปอเรเตอร์
Holger

แก้ไขคำตอบของคุณและเริ่มต้น AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi

บางที anwser นี้ควรได้รับการอัพเดตพร้อมกับอธิบายไว้ในอันนี้เกี่ยวกับการซิงโครไนซ์บนวัตถุเอง: stackoverflow.com/a/10324280/1099452
lucasvc

71

ซิงโครไนซ์กับการประกาศเมธอดคือน้ำตาลสำหรับประโยคนี้:

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

ในวิธีการคงที่มันเป็นน้ำตาล syntactical สำหรับเรื่องนี้:

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

ฉันคิดว่าถ้านักออกแบบ Java รู้แล้วตอนนี้สิ่งที่เข้าใจได้เกี่ยวกับการซิงโครไนซ์พวกเขาจะไม่เพิ่มน้ำตาลในประโยคเพราะมันบ่อยกว่าไม่นำไปสู่การปรับใช้ที่เกิดขึ้นพร้อมกัน


3
ไม่จริง. วิธีการซิงโครไนซ์สร้างไบต์ที่แตกต่างจากการซิงโครไนซ์ (วัตถุ) ในขณะที่ฟังก์ชั่นเทียบเท่ามันเป็นมากกว่าน้ำตาลประโยค
Steve Kuo

10
ฉันไม่คิดว่า "น้ำตาลในประโยค" มีการกำหนดอย่างเคร่งครัดว่าเทียบเท่ากับรหัสไบต์ ประเด็นคือมันเทียบเท่ากับหน้าที่
Yishai

1
หากนักออกแบบ Java ได้รู้จักสิ่งที่ถูกแล้วที่รู้จักกันเกี่ยวกับการตรวจสอบพวกเขาจะต้อง / ควรจะได้ทำมันแตกต่างกันโดยทั่วไปแทนการลอกเลียนแบบอวัยวะภายในของยูนิกซ์ ต่อ Brinch แฮนเซนกล่าวว่า 'เห็นได้ชัดว่าผมได้ทำงานเปล่าดาย' เมื่อเขาเห็น Java วิทยาการเห็นพ้องด้วย
มาร์ควิสแห่ง Lorne

นี่เป็นเรื่องจริง ตัวอย่างที่ให้โดย OP จะปรากฏขึ้นเพื่อล็อคแต่ละวิธี แต่ในความเป็นจริงพวกเขาทั้งหมดล็อคบนวัตถุเดียวกัน ไวยากรณ์หลอกลวงมาก หลังจากใช้ Java มานานกว่า 10 ปีฉันไม่รู้เรื่องนี้ ดังนั้นฉันจะหลีกเลี่ยงวิธีการทำข้อมูลให้ตรงกันด้วยเหตุผลนี้ ฉันมักจะคิดว่าวัตถุที่มองไม่เห็นถูกสร้างขึ้นสำหรับแต่ละวิธีที่กำหนดด้วยการซิงโครไนซ์
Peter Quiring

21

จาก "The Java ™ Tutorials" บนวิธีการซิงโครไนซ์ :

ครั้งแรกเป็นไปไม่ได้สำหรับการเรียกใช้วิธีการซิงโครไนซ์สองครั้งบนวัตถุเดียวกันเพื่อแทรกสอด เมื่อเธรดหนึ่งกำลังดำเนินการวิธีการซิงโครไนซ์สำหรับวัตถุเธรดอื่นทั้งหมดที่เรียกใช้วิธีการซิงโครไนส์สำหรับบล็อกวัตถุเดียวกัน (ระงับการดำเนินการ) จนกระทั่งเธรดแรกเสร็จสิ้นกับวัตถุ

จาก "The Java ™ Tutorials" บนบล็อกที่ซิงโครไนซ์ :

งบที่ซิงโครไนซ์ยังมีประโยชน์สำหรับการปรับปรุงการทำงานพร้อมกันด้วยการซิงโครไนซ์แบบละเอียด ตัวอย่างเช่นสมมติว่าคลาส MsLunch มีสองฟิลด์อินสแตนซ์ c1 และ c2 ที่ไม่เคยใช้ร่วมกัน การอัปเดตทั้งหมดของฟิลด์เหล่านี้จะต้องซิงโครไนซ์แต่ไม่มีเหตุผลที่จะป้องกันไม่ให้การอัปเดตของ c1 ถูกขัดจังหวะด้วยการอัปเดตของ c2 - และการทำเช่นนั้นจะลดการทำงานพร้อมกันโดยการสร้างบล็อกที่ไม่จำเป็น แทนที่จะใช้วิธีการซิงโครไนซ์หรือใช้การล็อกที่เกี่ยวข้องกับสิ่งนี้เราสร้างวัตถุสองอย่างเพื่อให้ล็อค

(เน้นเหมือง)

สมมติว่าคุณมีตัวแปรที่ไม่สอดแทรก 2 ตัว ดังนั้นคุณจึงต้องการเข้าถึงแต่ละรายการจากเธรดที่แตกต่างกันในเวลาเดียวกัน คุณต้องกำหนดล็อคไม่ได้อยู่ในคลาสวัตถุ แต่ในคลาสวัตถุเช่นด้านล่าง (ตัวอย่างจากลิงค์ 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++;
        }
    }
}

14

การเข้าถึงการล็อกอยู่บนวัตถุไม่ใช่วิธีการ ตัวแปรใดที่เข้าถึงได้ภายในวิธีนั้นไม่เกี่ยวข้อง

การเพิ่ม "การซิงโครไนซ์" ไปยังเมธอดหมายถึงเธรดที่รันโค้ดต้องได้รับการล็อกบนวัตถุก่อนที่จะดำเนินการต่อ การเพิ่ม "การซิงโครไนซ์แบบคงที่" หมายถึงเธรดที่ใช้รหัสต้องได้รับการล็อคบนวัตถุคลาสก่อนดำเนินการต่อ อีกวิธีหนึ่งคุณสามารถตัดโค้ดในบล็อกดังนี้:

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

เพื่อให้คุณสามารถระบุวัตถุที่ต้องได้รับการล็อค

หากคุณต้องการหลีกเลี่ยงการล็อควัตถุที่มีคุณสามารถเลือกระหว่าง:


7

จากลิงค์เอกสารของ oracle

ทำให้วิธีการทำข้อมูลให้ตรงกันมีสองผล:

ครั้งแรกเป็นไปไม่ได้สำหรับการเรียกใช้วิธีการซิงโครไนซ์สองครั้งบนวัตถุเดียวกันเพื่อแทรกสอด เมื่อเธรดหนึ่งกำลังดำเนินการวิธีการซิงโครไนซ์สำหรับวัตถุเธรดอื่นทั้งหมดที่เรียกใช้วิธีการซิงโครไนส์สำหรับบล็อกวัตถุเดียวกัน (ระงับการดำเนินการ) จนกระทั่งเธรดแรกเสร็จสิ้นด้วยวัตถุ

ประการที่สองเมื่อเมธอดซิงโครไนซ์ออกแล้วมันจะสร้างความสัมพันธ์ที่เกิดขึ้นก่อนโดยอัตโนมัติกับการเรียกใช้เมธอดซิงโครไนซ์ที่ตามมาสำหรับวัตถุเดียวกัน สิ่งนี้รับประกันได้ว่าการเปลี่ยนแปลงสถานะของวัตถุสามารถมองเห็นได้โดยเธรดทั้งหมด

ดูที่หน้าเอกสารนี้เพื่อทำความเข้าใจกับการล็อคภายในและพฤติกรรมการล็อค

สิ่งนี้จะตอบคำถามของคุณ: บนวัตถุเดียวกัน x คุณไม่สามารถเรียก x.addA () และ x.addB () ในเวลาเดียวกันเมื่อหนึ่งในวิธีการซิงโครไนซ์ดำเนินการอยู่


4

หากคุณมีวิธีการบางอย่างที่ไม่ได้ซิงโครไนซ์และกำลังเข้าถึงและเปลี่ยนแปลงตัวแปรอินสแตนซ์ ในตัวอย่างของคุณ:

 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


3

คุณสามารถทำสิ่งต่อไปนี้ ในกรณีนี้คุณกำลังใช้การล็อก a และ b เพื่อซิงโครไนซ์แทนที่จะล็อคใน "นี่" เราไม่สามารถใช้ int ได้เนื่องจากค่าดั้งเดิมไม่มีการล็อคดังนั้นเราจึงใช้ Integer

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

ใช่มันจะปิดกั้นวิธีอื่น ๆ เนื่องจากวิธีการซิงโครไนซ์จะใช้กับวัตถุคลาสWHOLEตามที่ระบุ .... แต่อย่างไรก็ตามจะบล็อกการดำเนินการเธรดอื่น ๆเท่านั้นในขณะที่ดำเนินการหาผลรวมในวิธีใดก็ตาม addA หรือ addB เข้าสู่เพราะเมื่อเสร็จ ... ด้ายหนึ่งจะรับฟรีวัตถุและหัวข้ออื่น ๆ จะเข้าถึงวิธีการอื่น ๆ และอื่น ๆ ทำงานได้อย่างสมบูรณ์

ฉันหมายถึง "การซิงโครไนซ์" ถูกสร้างขึ้นมาอย่างแม่นยำสำหรับการบล็อกเธรดอื่นจากการเข้าถึงอื่นในขณะที่ใช้งานโค้ดเฉพาะ ดังนั้นในที่สุดรหัสนี้จะทำงานได้ดี

เป็นบันทึกสุดท้ายหากมีตัวแปร 'a' และ 'b' ไม่เพียง แต่เป็นตัวแปรเฉพาะ 'a' หรือชื่ออื่น ๆ ไม่จำเป็นต้องซิงโครไนซ์วิธีการนี้ทำให้ปลอดภัยอย่างสมบูรณ์เมื่อเทียบกับ var อื่น ๆ (หน่วยความจำอื่น สถานที่)

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

จะได้ผลเช่นกัน


2

ตัวอย่างนี้ (แม้ว่าจะไม่ใช่รูปสวย) สามารถให้ข้อมูลเชิงลึกเกี่ยวกับกลไกการล็อคได้มากขึ้น หากincrementAมีการทำข้อมูลให้ตรงกันและincrementBจะไม่ตรงกันแล้วincrementBจะดำเนินการให้เร็วที่สุด แต่ถ้าincrementBนอกจากนี้ยังมีข้อมูลให้ตรงกันแล้วมันมีการ 'รอ' สำหรับincrementAให้เสร็จก่อนที่จะincrementBสามารถทำในงานของตน

ทั้งสองวิธีจะเรียกว่าเข้าสู่ตัวอย่างเดียว - วัตถุในตัวอย่างนี้มันคืองานและ 'การแข่งขัน' กระทู้มีaThreadและหลัก

ลองใช้ ' ทำข้อมูลให้ตรงกัน ' ในincrementBโดยที่ไม่มีและคุณจะเห็นผลลัพธ์ที่แตกต่างกันถ้าincrementBเป็น ' ทำข้อมูลให้ตรงกัน ' เช่นกันคุณต้องรอการเพิ่ม A () เพื่อเสร็จสิ้น เรียกใช้หลาย ๆ ครั้งในแต่ละรุ่น

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();
    }
}

1

ในการซิงโครไนซ์ java ถ้าเธรดต้องการป้อนวิธีการซิงโครไนซ์มันจะได้รับการล็อกบนวิธีการซิงโครไนส์ทั้งหมดของวัตถุนั้นไม่ใช่เฉพาะในวิธีการซิงโครไนส์หนึ่งวิธีที่เธรดใช้ ดังนั้นเธรดที่เรียกใช้งาน addA () จะได้รับการล็อกบน addA () และ addB () เนื่องจากทั้งคู่ถูกซิงโครไนซ์ดังนั้นเธรดอื่นที่มีวัตถุเดียวกันจึงไม่สามารถเรียกใช้ addB ()


0

สิ่งนี้อาจไม่ทำงานเนื่องจากการชกมวยและ autoboxing จาก Integer ถึง int และ viceversa ขึ้นอยู่กับ JVM และมีความเป็นไปได้สูงที่ตัวเลขสองตัวที่แตกต่างกันอาจถูกแฮชไปยังที่อยู่เดียวกันหากอยู่ระหว่าง -128 ถึง 127

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.