ว่าด้วยเรื่อง “Synchronization” ใน Java

Phayao Boonon
3 min readOct 9, 2018

--

ใน Concurrency applicaiton นั้นจะเป็นสิ่งแวดล้อมที่มี thread ทำงานกันหลาย thread แต่ว่าถ้า thread เหล่านั้นต้องการเข้าถึงทรัพยากร (resource) เดียวกัน มันก็จะมีโอกาศที่ thread มากกว่าหนึ่งจะเข้าถึงทรัพยากรในเวลาที่พร้อมกัน และก็จะทำให้การทำงานของ multi-thread มีปัญหาได้ ซึ่งปัญหานี้ Java ได้มีกลไลรองรับไว้แล้วนั้นก็คือการให้ thread มีการประสานข้อมูลกัน (Synchronizing) ในการเข้าถึงทรัพยากรเดียวกันนี้

การประสานข้อมูลกันนี้ทำให้ thread ใด thread หนึ่งทำงานเพียง thread เดียว ณ ช่วงเวลาใดเวลาหนึ่ง และบล็อก thread อื่นไม่ให้ทำงานจนกว่า thread นั้นจะทำงานเสร็จเรียบร้อย

ทำไมต้อง Synchronization

ลองพิจารณา race condition คลาสที่มี method increase() ที่เพิ่มค่าของ c ซึ่งเป็นทรัพยากรร่วมกันของ thread โดยจะรันหลายๆ thread ที่เรียก method increase()

class Counter {
private static int c = 0;

public static void increase() {
c++;
}

public static int value() {
return c;
}
}

ในคลาส Demo จะรัน 2 thread พร้อมกัน เพื่อเรียก method increase 10000 ครั้ง

public class Demo {
public static void main(String[] args)
throws InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(2);

IntStream.range(0, 10000).forEach(i ->
executors.submit(Counter::increase));

executors.awaitTermination(100, TimeUnit.MILLISECONDS);

System.out.println(Counter.value());

}
}

Output

9981

ซึ่งจะเห็นได้ว่า output จะเป็นตัวเลข random ที่ไม่ได้ตัวเลข 10000 เสมอเนื่องจากมีการ failed เนื่องจากเรียก method increase เพื่อเข้าถึง c พร้อมกัน นี่คือปัญหาของการเข้าถึงทรัพยากรเดียวกันของ thread มากกว่าหนึ่ง thread ที่ทำงานพร้อมกัน

ดังนั้น Synchronization จึ่งจะมาแก้ปัญหานี้

Synchronized keyword

Synchronized keyword สามารถใช้ได้ในระดับที่แตกต่างกัน ดังนี้

  • Instance method
  • Static method
  • Code block

เมื่อใช้ Synchronized code block นั้น Java จะใช้วิธีการ monitor คือ monitor lock หรือ intensic lock ในการทำ Sychronization

Synchronized — Instance method

การใช้ synchronized keyword กับ instance method ทำได้ด้วยการเพิ่ม synchronized เข้าไปในการประกาศ instance method เพื่อทำให้ method เป็น Synchronization

class Counter {
private int c = 0;

public synchronized void increase() {
c++;
}

public int value() {
return c;
}
}

Synchronized method จะ synchronization บน instance ของ class ที่เป็นเจ้าของ method นั้นหมายความว่า 1 thread ต่อ instance ของ class ที่สามารถ invoke ให้ method ทำงาน จะทำให้ output ออกมาเป็น 10000 เสมอ

public class Demo {
public static void main(String[] args)
throws InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(2);

Counter counter = new Counter();

IntStream.range(0, 10000).forEach(i ->
executors.submit(counter::increase));

executors.awaitTermination(100, TimeUnit.MILLISECONDS);

System.out.println(counter.value());

}
}

Output

10000

Synchronized — Static method

การใช้ synchronized keyword กับ static method ทำได้ด้วยการเพิ่ม synchronized เข้าไปในการประกาศ static method เพื่อทำให้ method เป็น Synchronization คล้ายกับ instance method

class Counter {
private static int c = 0;

public static synchronized void increase() {
c++;
}

public static int value() {
return c;
}
}

Synchronized method จะ synchronization บน Class object ที่เชื่อมกันด้วยคลาสและเพียง 1 Class object เท่านั้นที่อยู่ใน JVM เพียงหนึ่ง thread ทำงานและ invoke ให้ static method ทำงานได้ในช่วงเวลาใดเวลาหนึ่งเท่านั้นและจะบล็อก thread อื่นไม่ให้ invoke static method นี้ จะทำให้ output ออกมาเป็น 10000 เสมอ

public class Demo {
public static void main(String[] args)
throws InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(2);

IntStream.range(0, 10000).forEach(i ->
executors.submit(Counter::increase));

executors.awaitTermination(100, TimeUnit.MILLISECONDS);

System.out.println(Counter.value());

}
}

Output

10000

Synchronized — Code block

บางทีเราไม่ต้องการทำ synchronization ทั้ง method แต่ว่าต้องการเพียงแค่บางส่วนของโค้ดเท่านั้น ซึ่งสามารถใช้ synchronized keyword ครอบส่วนของโค้ดที่เราต้องการทำให้มันทำงานสอดคล้องกัน

class Counter {
private int c = 0;

public void increase() {
synchronized (this) {
c++;
}
}

public int value() {
return c;
}
}

จะเห็นได้ว่าเราใส่ this ใน synchronized block ซึ่งเป็นการบ่งบอกว่าให้ instance นี้เป็น monitor object โค้ดที่อยู่ใน block เป็น synchronization ที่ทำงานสอดคล้องกัน ซึ่งมีเพียง 1 thread ต่อ monitor object ที่สามารถ execute ส่วนที่อยู่ใน block ได้ จะทำให้ output ออกมาเป็น 10000 เสมอ

public class Demo {
public static void main(String[] args)
throws InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(2);

Counter counter = new Counter();

IntStream.range(0, 10000).forEach(i ->
executors.submit(counter::increase));

executors.awaitTermination(100, TimeUnit.MILLISECONDS);

System.out.println(counter.value());

}
}

Output

10000

แต่ถ้ากรณี static method ที่ไม่สามารถใช้ this keyword ได้ เราจะส่ง class name เข้าไปใน syncronized block ซึ่งจะทำให้คลาสเป็น monitor object เพื่อ syncronization monitoring

class Counter {
private static int c = 0;

public static void increase() {
synchronized (Counter.class) {
c++;
}
}

public static int value() {
return c;
}
}
public static void main(String[] args)
throws InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(2);

IntStream.range(0, 10000).forEach(i ->
executors.submit(Counter::increase));

executors.awaitTermination(100, TimeUnit.MILLISECONDS);

System.out.println(Counter.value());

}
}

อ้างอิง

--

--

Phayao Boonon
Phayao Boonon

Written by Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish

No responses yet