เหตุใดเธรดตัวแปรโลคัลจึงปลอดภัยใน Java


93

ฉันกำลังอ่านมัลติเธรดใน Java และฉันเจอสิ่งนี้

ตัวแปรโลคัลคือเธรดที่ปลอดภัยใน Java

ตั้งแต่นั้นมาฉันก็คิดว่าตัวแปรในเครื่องมีความปลอดภัยอย่างไร / ทำไม

ใครช่วยแจ้งให้เราทราบได้


27
เนื่องจากมีการจัดสรรใน Stack และเธรดไม่ใช้สแต็ก.. มันไม่ซ้ำกันสำหรับแต่ละอัน ..
Rohit Jain

คำตอบ:


104

เมื่อคุณสร้างเธรดจะมีการสร้างสแต็กของตัวเอง เธรดสองเธรดจะมีสองสแต็กและเธรดหนึ่งเธรดจะไม่แชร์สแต็กกับเธรดอื่น

ตัวแปรโลคัลทั้งหมดที่กำหนดไว้ในโปรแกรมของคุณจะได้รับการจัดสรรหน่วยความจำในสแต็ก (ตามที่ Jatin แสดงความคิดเห็นหน่วยความจำในที่นี้หมายถึงค่าอ้างอิงสำหรับอ็อบเจ็กต์และค่าสำหรับประเภทดั้งเดิม) (แต่ละวิธีการเรียกโดยเธรดจะสร้างสแต็กเฟรมบนสแต็กของตัวเอง) ทันทีที่เธรดนี้ดำเนินการเมธอดเสร็จสแต็กเฟรมจะถูกลบออก

มีการบรรยายที่ยอดเยี่ยมโดยศาสตราจารย์ Stanford ใน youtubeซึ่งอาจช่วยคุณในการทำความเข้าใจแนวคิดนี้


13
ขออภัยคุณคิดผิดมีเพียงตัวแปรท้องถิ่นดั้งเดิมเท่านั้นที่เก็บไว้ในสแต็ก ส่วนที่เหลือตัวแปรทั้งหมดจะถูกเก็บไว้ใน Heap Java 7 แนะนำการวิเคราะห์การหลีกเลี่ยงซึ่งสำหรับตัวแปรบางตัวอาจจัดสรรเป็นสแต็ก
Jatin

6
สแต็คเก็บเฉพาะการอ้างอิงไปยังวัตถุบนฮีป เนื่องจากสแต็กได้รับการล้างข้อมูลอ้างอิงก็เช่นกัน จึงสามารถเก็บขยะได้
Jatin

6
@Jatin: คุณถูกต้อง เมื่อฉันหมายถึงหน่วยความจำฉันหมายถึงค่าอ้างอิงสำหรับวัตถุและค่าสำหรับวัตถุดั้งเดิม (ฉันคิดว่านักพัฒนามือใหม่รู้ด้วยว่า Objects อยู่บนฮีป)
kosa

2
@Nambari แต่ถ้าค่าอ้างอิงชี้ไปที่ตัวแปรที่ใช้ร่วมกัน แล้วเราจะบอกว่าปลอดภัยด้ายได้อย่างไร?
H. Rabiee

3
@hajder: อะไรทำให้ตัวแปรเป็นแชร์ เริ่มจากตรงนั้น ตัวแปรอินสแตนซ์หรือคลาสใช่ไหม ไม่ใช่ตัวแปรท้องถิ่นและอ่านคำตอบของ Marko Toplink ในหัวข้อนี้ฉันคิดว่านั่นเป็นประเด็นที่คุณสับสน
kosa

19

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

public void someMethod(){

   long threadSafeInt = 0;

   threadSafeInt++;
}

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


มีข้อผิดพลาดเกิดขึ้นโปรดดูความคิดเห็นของ @Nambari response
Jatin

หากคุณกำลังชี้ไปที่ความจริงที่ว่า localSafeInt จะยังคงเป็น 0 ดังนั้น 1 แล้วจึงลบออกไปนั่นก็ดี ดังนั้นจึงแสดงให้เห็นว่าตัวแปรนี้ไม่ได้ใช้ร่วมกันระหว่างเธรดและจึงไม่ได้รับผลกระทบจากการเธรดหลายเธรด .. ฉันคิดว่าคุณสามารถชี้ให้เห็นได้อีกเล็กน้อยว่า threadsafe มักจะเป็น 0 หรือ 1 เสมอ
tObi

14

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

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

พิจารณาสองกรณีนี้:

public class NotThreadsafe {
    int x = 0;
    public int incrementX() {
        x++;
        return x;
    }
}

public class Threadsafe {
    public int getTwoTimesTwo() {
        int x = 1;
        x++;
        return x*x;
    }
}

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


6

การเรียกใช้แต่ละเมธอดมีตัวแปรภายในของตัวเองและเห็นได้ชัดว่าการเรียกใช้เมธอดเกิดขึ้นในเธรดเดียว ตัวแปรที่อัปเดตโดยเธรดเดียวเท่านั้นที่จะปลอดภัยต่อเธรดโดยเนื้อแท้

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


1
คุณบอกว่า "การเรียกใช้เมธอดบนวัตถุที่อ้างถึงนั้นไม่ปลอดภัยต่อเธรดโดยเนื้อแท้" แต่อ็อบเจ็กต์ที่อ้างถึงโดยวิธีการอ้างอิงภายใน - อินสแตนซ์ในขอบเขตวิธีนี้ - สามารถแชร์โดยสองเธรดได้อย่างไร คุณช่วยชี้ตัวอย่างได้ไหม
Akshay Lokur

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

6

นอกจากคำตอบอื่น ๆ เช่น Nambari's

ฉันต้องการชี้ให้เห็นว่าคุณสามารถใช้ตัวแปรท้องถิ่นในวิธีการประเภท anoymous:

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

พิจารณารหัสที่ผิดกฎหมายนี้:

public void nonCompilableMethod() {
    int i=0;
    for(int t=0; t<100; t++)
    {
      new Thread(new Runnable() {
                    public void run() {
                      i++; //compile error, i must be final:
                      //Cannot refer to a non-final variable i inside an
                      //inner class defined in a different method
                    }
       }).start();
     }
  }

หาก java อนุญาตสิ่งนี้ (เช่น C # ทำผ่าน "การปิด") ตัวแปรภายในจะไม่ปลอดภัยในทุกสถานการณ์อีกต่อไป ในกรณีนี้ค่าของในตอนท้ายของกระทู้ทั้งหมดที่ไม่ได้รับประกันว่าจะi100


สวัสดีเวสตันจากการสนทนาข้างต้นและคำตอบด้านล่างฉันเข้าใจว่า java ช่วยให้มั่นใจได้ว่าเธรดปลอดภัยสำหรับตัวแปรในเครื่องทั้งหมด ฉันขอทราบว่าการใช้คีย์เวิร์ดซิงโครไนซ์จริงคืออะไร? คุณช่วยอธิบายด้วยตัวอย่างเช่นนี้ได้ไหม
Prabhu

5

เธรดจะมีกองของตัวเอง เธรดสองเธรดจะมีสองสแต็กและเธรดหนึ่งเธรดจะไม่แชร์สแต็กกับเธรดอื่น ตัวแปรภายในจะถูกเก็บไว้ในสแต็กของแต่ละเธรด นั่นหมายความว่าจะไม่มีการแชร์ตัวแปรภายในระหว่างเธรด


3

โดยทั่วไปมีพื้นที่เก็บข้อมูลสี่ประเภทใน java เพื่อจัดเก็บข้อมูลคลาสและข้อมูล:

พื้นที่วิธี, กอง, กอง JAVA, พีซี

ดังนั้นพื้นที่วิธีการและฮีปจะถูกแชร์โดยเธรดทั้งหมด แต่ทุกเธรดจะมี JAVA Stack และ PC ของตัวเองและเธรดอื่น ๆ จะไม่แชร์

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

@ เวสตันยังให้คำตอบที่ดี


1

เฉพาะ ตัวแปรโลคัลเท่านั้นที่ถูกเก็บไว้ในเธรดสแต็ก

ตัวแปรท้องถิ่นที่primitive type(เช่น int, long ... ) จะถูกเก็บไว้ในthread stackและเป็นผลให้เธรดอื่นไม่มีการเข้าถึง

ตัวแปรท้องถิ่นที่reference type(ตัวต่อของObject) ประกอบด้วยจาก 2 ส่วน - ที่อยู่ (ซึ่งถูกเก็บไว้บนthread stack) และวัตถุ (ซึ่งถูกเก็บไว้บนheap)


class MyRunnable implements Runnable() {
    public void run() {
        method1();
    }

    void method1() {
        int intPrimitive = 1;

        method2();
    }

    void method2() {
        MyObject1 myObject1 = new MyObject1();
    }
}

class MyObject1 {
    MyObject2 myObject2 = new MyObject2();
}

class MyObject2 {
    MyObject3 myObject3 = MyObject3.shared;
}

class MyObject3 {
    static MyObject3 shared = new MyObject3();

    boolean b = false;
}

ป้อนคำอธิบายภาพที่นี่

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