Cách xử lý bất đồng bộ java
Tài khoản ngân hàng của công ty X có 10 triệu. Tại một thời điểm, giám đốc rút thẻ thanh toán số tiền 5 triệu sau một bữa giao lưu với đối tác. Cùng lúc đó, tại văn phòng, nhân viên tài chính chuyển 7 triệu để mua sắm chuẩn bị cho sự kiện ngày mai. Chuyện gì sẽ xảy ra? Nếu cả hai giao dịch trên đều được thực hiện thì tổng cộng công ty X đã chi hết 12 triệu trong khi tài khoản mình chỉ có 10 triệu. Ngân hàng sẽ chịu lỗ 2 triệu. Bài toán trên cũng chính là một ví dụ kinh điển mỗi khi chúng ta đề cập đến khía cạnh đối lập nhau là đồng bộ (Synchronized) và bất đồng bộ (Unsynchronized). Hai khái niệm đối lập nhau này cũng chính là chủ đề để chúng ta cùng tìm hiểu sâu hơn trong bài viết này. Lý do phát sinh vấn đềNhìn lại bài toán ngân hàng, chúng ta có thể dễ dàng nhận ra được mấu chốt để phát sinh việc công ty X dùng quá số tiền so với tài khoản hiện có chính là việc có 2 giao dịch cùng một thời điểm. Giả dụ nếu người giám đốc hoàn tất thanh toán tiền vào lúc 12 giờ trưa, người nhân viên tài chính chuyển tiền vào lúc 3 giờ chiều, lúc này tài khoản công ty đã cập nhật còn 5 triệu, thì sẽ không có việc nhầm lẫn gì ở đây. Đa luồng (Multi-thread) cùng truy cập vào một tài nguyên chính là nguyên nhân gây ra sự sai lệch của giả dụ đầu bài. Do đây không phải là bài chuyên về luồng (Thread) nên mình sẽ chỉ nói về cách mà các luồng gây sai lệch. Ví dụ dưới đây sẽ giúp các bạn có cái nhìn rõ ràng hơn. Mình sẽ mô tả lại trường hợp ví dụ một cách tương đối. Bằng cách tạo ra hai luồng khác nhau cùng truy cập vào một tài nguyên, mình liên tục kiểm tra số tiền hiện tại rồi trừ đi một và in ra giá trị còn lại.
Sau khi chạy đoạn code trên, mình nhận được kết quả như sau. Chúng ta có thể thấy được ở lượt thứ 0 của Thread 2 đã xảy ra lỗi. Giá trị ban đầu vốn là 10, sau khi trừ đi 1 thì lại chỉ còn 8. Lý do là bởi vì trong thời gian giữa lệnh in ra giá trị ban đầu và lệnh in ra giá trị sau khi trừ của Thread 2 thì Thread 1 đã thực hiện giảm giá trị của biến Việc sử dụng đa luồng giúp cho việc thực thi các hành động diễn ra nhanh hơn bằng cách để các luồng chạy đan xen lẫn nhau. Nhưng cũng chính vì đó mà nếu không cẩn thận, đa luồng sẽ ảnh hưởng đến việc thực thi hành động của luồng khác thông qua các tài nguyên dùng chung như biến value ở chương trình mẫu bên trên. Để tránh việc gây nhiễu giữa các luồng với nhau, khái niệm đồng bộ và bất đồng bộ đã ra đời. Bất đồng bộ cũng tựa như cách mà chương trình mẫu bên trên thực hiện, mặc kệ các luồng thích làm gì thì làm. Đồng bộ, phiên bản trái ngược với bất đồng bộ, giữ cho giá trị của các tài nguyên dùng chung không bị sai lệch. Bạn đã từng đi qua trạm thu phí bao giờ chưa? Trong quá trình tham gia giao thông, các phương tiện có thể vượt mặt nhau để đi trước nhưng khi đến trạm thu phí thì các xe phải xếp hàng vượt trạm từng chiếc một. Xong chiếc này thì sẽ đến chiếc khác. Quy tắc của đồng bộ cũng như vậy, bằng cách gọi từ khóa synchronized, một bộ phận tài nguyên sẽ bị khóa lại chỉ cho phép một luồng sử dụng trong một thời điểm. Khi nào luồng đó thực hiện xong, quyền sử dụng tài nguyên sẽ được giao lại cho luồng khác. Các kiểu đồng bộ và làm sao để thực hiện chúng?Việc thực hiện đồng bộ có thể được thực hiện trên đối tượng (đồng bộ phương thức, đồng bộ khối) và trên lớp.
Kết quả khi không đồng bộ Tạm kếtHi vọng bài viết này giúp các bạn nhận thức rõ hơn về các khái niệm đồng bộ và bất đồng bộ. Việc sử dụng synchronized thì sẽ giúp yên tâm hơn trong việc dùng đa luồng, miễn là bạn đặt từ khóa ở đúng chỗ :) Tuy nhiên, mình cũng khẳng định để có thể thông thạo việc sử dụng đồng bộ đối với các tiến trình đa luồng là một việc không phải chỉ bởi xem qua một vài ví dụ là biết hết. Chỉ khi bắt tay vào làm thử và học hỏi thì mới có thể hiểu rõ hơn cách mà các dòng lệnh hoạt động. |