[Training] Học Java từ những điều cơ bản p2 - Volatile in thread
Volatile là gì? Và bạn đã bao sử dụng nó trong lập trình Java chưa? Cách đây vài năm, nhớ là hồi đó mình mới bập bõm học lập trình thì phải, mình có bắt gặp thuật ngữ "volatile" nhưng mình cũng chẳng quan tâm nó nên lờ đi (hồi đó suy nghĩ thiển cận lắm, cái gì ko được dạy thì khỏi dùng luôn). Đến ...
Volatile là gì? Và bạn đã bao sử dụng nó trong lập trình Java chưa?
Cách đây vài năm, nhớ là hồi đó mình mới bập bõm học lập trình thì phải, mình có bắt gặp thuật ngữ "volatile" nhưng mình cũng chẳng quan tâm nó nên lờ đi (hồi đó suy nghĩ thiển cận lắm, cái gì ko được dạy thì khỏi dùng luôn). Đến sau đó mình có gặp phải 1 trường hợp lỗi với singleton pattern khi khởi tạo trên nhiều thread và bắt đầu google thì mới chính thức dùng cái này.
Trong những ví dụ mình xem trên github thì thấy thằng này hay đi cùng với modifier "static", và trong bài này mình cũng giới thiệu đến cho những bạn chưa rõ 2 modifier này khác nhau và cách dùng trong lập trình java như nào. Bạn nào biết rồi thì kiểm tra giúp mình xem mình còn hiểu sai ở chỗ nào không nhé ^^
I. Static vs Volatile
1. Static
Static member là đối tượng trực thuộc 1 class, chứ ko nằm trong 1 thể hiện nhất định nào cả hay nói chính xác hơn nó thuộc class chứ không thuộc instance, và giá trị của nó có thể được dùng ở nhiều thể hiện khác nhau. Nếu 2 thread khác nhau cùng update giá trị của static member thì những đối tượng dùng nó có thể lấy được giá trị cũ chứ không phải giá trị mới nhất.
public class Hello { // value / method public static String staticValue; public String nonStaticValue; } class A { Hello hello = new Hello(); hello.staticValue = "abc"; hello.nonStaticValue = "xyz"; } class B { Hello hello2 = new Hello(); // here staticValue = "abc" hello2.staticValue; // will have value of "abc" hello2.nonStaticValue; // will have value of null }
2. Volatile
Volatile member là 1 đối tượng nằm trên bộ nhớ. Tất cả các thể hiện khác đều có thể thấy được giá trị của nó, sau khi giá trị này được thay đổi kể cả ở khác thread.
public class Main { public static volatile int a = 0; public static void main(String args[]) throws InterruptedException{ List<Thread> list = new ArrayList<Thread>(); for(int i = 0 ; i<11 ;i++){ list.add(new Pojo()); } for (Thread thread : list) { thread.start(); } Thread.sleep(20000); System.out.println(a); } }
3. Sự khác nhau
Chúng ta lại quay lại ví dụ với singleton pattern. Các bạn xem 2 ví dụ dưới đây xem có gì bất thường không
vd1:
class Singleton { private static Singleton _instance; public static Singleton getInstance() { if (_instance == null) { _instance = new Singleton(); } return _instance; } }
vd2:
class Singleton { private volatile static Singleton _instance; private Singleton() { // preventing Singleton object instantiation from outside } /* * 1st version: creates multiple instance if two thread access * this method simultaneously */ public static Singleton getInstance() { if (_instance == null) { _instance = new Singleton(); } return _instance; }
Và chúng ta sẽ tiến hành khởi tạo class Singleton trên ở những thread song song với nhau xem có điều gì xảy ra nhé.
Với vd1 chúng ta có thể tạo nhiều thể hiện Singleton (cái này bị sai so với mong muốn của chúng ta là muốn 1 thể hiện duy nhất cho đối tượng Singleton)
còn với vd2 thì dùng bạn có bao nhiêu thread đi nữa bạn vẫn luôn có 1 thể hiện Singleton duy nhất.
Đây là lý do tại sao nhiều khi chúng ta hoạt động với multi-thread mà gặp phải lỗi concurency exception hoặc instance của singleton bị null tại 1 thời điểm nào đó mà chúng ta không kiểm soát được.
Chắc hẳn cũng có bạn hỏi tôi nếu ở vd1 chúng ta dùng thread-safe thì sao
public static Singleton getInstanceDC() { if (_instance == null) { // Single Checked synchronized (Singleton.class) { if (_instance == null) { // Double checked _instance = new Singleton(); } } } return _instance; }
Và làm điều này thì có nghĩa bạn đang lãng phí memory của hệ thống cho cơ chế synchronize không cần thiết, và lúc này thread sẽ bị block lại, nếu bạn đã từng thử điều này thì bạn sẽ thấy hệ thống của bạn bị lag.
Và chỉ cần 1 modifier đơn giản valotide đơn giản là chúng ta có thể giải quyết được những phiền nhiễu trên.
II. Volatile vs Synchornized
- Volatile là field modifier (bổ trợ của trường) còn synchronized là code blocks and methods modifier
- Synchronized cần khóa (đóng khi đang xử lý và mở sau khi xử lý) thread còn volatile thì không
- Threads trong java có thể bị khóa lại trong khi chờ đợi các tiến trình khác của hệ thống synchronized, điều này ko xảy ra với volatile
- Synchronized chiếm nhiều tài nguyên bộ nhớ hơn là volatile
- Volatile là đồng bộ dữ liệu trên 1 đối tượng nhất định giữa các thread, còn synchronized là đồng bộ giá trị của các biến giữa các thread khác nhau
- không thể synchronized trên 1 đối tượng null còn volatile thì thoải mái
- Bắt đầu từ java 5 thì việc đọc và ghi dữ liệu cho volatile được cấp phát là giống nhau
III. Tổng kết
Đôi khi những vấn đề nhỏ trong lập trình thôi cũng khiến chúng ta gặp rất nhiều vấn đề, khiến chúng ta mất rất nhiều cống sức để tìm ra solution thích hợp, nhưng đến khi hiểu được thì mới thấy có những cách đơn giản để giải quyết chúng đến không ngờ. Mình hi vọng các bạn sẽ nắm vững từng dòng code của mình để chúng trở thành 1 phản xạ có điều kiện mỗi khi chúng ta được giao 1 bài toán, 1 task trong dự án. Và luôn đảm bảo dòng code của chúng ta là đơn giản nhất, dễ hiểu nhất, dễ maintain nhất nhé. Hẹn gặp lại các bạn trong các bài viết lần sau.
IV. Tham khảo
- http://javarevisited.blogspot.com/2011/06/volatile-keyword-java-example-tutorial.html
- http://javarevisited.blogspot.com/2014/05/double-checked-locking-on-singleton-in-java.html
- http://javarevisited.blogspot.com/2012/12/how-to-create-thread-safe-singleton-in-java-example.html
- http://stackoverflow.com/questions/106591/do-you-ever-use-the-volatile-keyword-in-java
- http://stackoverflow.com/questions/413898/what-does-the-static-keyword-do-in-a-class
- http://stackoverflow.com/questions/2423622/volatile-vs-static-in-java