- vừa được xem lúc

[Object-Oriented + Java] Bài viết #8 - Concurrency Programming

0 0 14

Người đăng: Semi Dev

Theo Viblo Asia

Nhóm các công cụ lập trình ứng dụng vận hành đa nhiệm được Java cung cấp kèm theo hai khái niệm là ProcessThread. Trong đó thì một Process mô tả một môi trường vận hành code hoàn chỉnh, và Thread là một dạng Process với thiết kế đơn giản hơn và tồn tại bên trong Process. Mỗi ứng dụng JVM mặc định là được khởi tạo với một Process, và mỗi Process được mặc định là được khởi tạo với một Thread.

Ở đây chúng ta sẽ không đề cập đến vấn đề quản lý ở cấp độ của các Process mà chỉ tìm hiểu về cách tạo ra các Thread.

Runnable

interface: java.lang.Runnable

Trước khi nghĩ tới việc thực hiện song song các tác vụ thì chúng ta cần một giao diện chung để định nghĩa các tác vụ. Thay vì object A mô tả một tác vụ có phương thức khởi chạy là .start()object B mô tả một tác vụ khác có phương thức khởi chạy là .execute(), thì ở đây chúng ta có Runnable để tạo giao diện lập trình chung cho các object mô tả các tác vụ đơn với phương thức duy nhất .run() để khởi chạy.

import java.io.*; class Main { public static void main (String[] $args) { Runnable $task = (() -> System.out.println ("Do something")); $task.run ();
} } // -- end class

Lúc này các object mô tả các tác vụ như $task được tạo ra đã có giao diện lập trình chung, nhưng vẫn chưa được thực thi trên các tiến trình riêng biệt. Bước tiếp theo là chúng ta cần tạo ra các tiến trình thực thi code độc lập so với tiến trình vận hành chính của phương thức main.

Thread

class: java.util.Thread

Chúng ta có thể sử dụng class Thread để tạo ra các object mô tả các tiến trình thực thi code độc lập theo hai cách: Bởi vì class Thread có triển khai Runnable vì vậy nên chúng ta có thể tạo ra các object thuộc class nặc danh và override phương thức .run(); Hoặc, chúng ta cũng có thể truyền một object Runnable vào trình khởi tạo của Thread.

import java.io.*; class Main { public static void main (String[] $args) { Runnable $task = (() -> { try { Thread.sleep (1000); System.out.println ("Extra thread"); } catch (Exception $exception) { System.out.println ($exception.toString ()); } }); // -- Runnable Thread $thread = new Thread ($task, "Thread name"); $thread.start (); System.out.println ("Main thread");
} } // -- end class

Ở đây chúng ta có câu lệnh Thread.sleep(1000); sẽ tạm dừng tiến trình của Thread đang thực thi $task. Tuy nhiên, do chúng ta đã đặt $task vào một tiến trình mới nên tiến trình của main không bị ảnh hưởng; Và kết quả là câu lệnh in ra dòng chữ "Main thread" sẽ được thực hiện trước so với câu lệnh in trong $task.

Main thread delay 1 second... Extra thread

Trong trường hợp muốn gộp tiến trình thực thi của một Thread với một tiến trình bất kỳ thì chúng ta cần giữ địa chỉ tham chiếu của Thread đó và gọi phương thức .join() trong tiến trình muốn gộp. Ở đây chúng ta sẽ thử gộp trở lại tiến trình chính main bằng cách gọi phương thức .join() ngay sau khi .start().

import java.io.*; class Main { public static void main (String[] $args) { Runnable $task = ... ; Thread $thread = new Thread ($task, "Thread name"); $thread.start (); try { $thread.join (); } catch (Exception $exception) { System.out.println ($exception.toString ()); } System.out.println ("Main thread"); } } // -- end class

Kết quả vận hành:

delay 1 second... Extra thread
Main thread

Synchronized

Ở bài viết trước, khi giới thiệu tổng quan về Java Collections Framework, chúng ta đã thấy Java có cung cấp thêm các phiên bản cấu trúc dữ liệu thread-safe. Cụm từ thread-safe có nghĩa là các cấu trúc này được thiết kế để đảm bảo rằng: trong trường hợp cùng lúc được truy xuất và chỉnh sửa bởi nhiều thread khác nhau thì kết quả hoạt động sẽ luôn ổn định giống như khi làm việc với một thread duy nhất.

Để hiểu rõ hơn ở điểm này, chúng ta sẽ xuất phát với một cấu trúc dữ liệu tự định nghĩa và giả định rằng cấu trúc này sẽ lưu trữ toàn bộ dữ liệu mô tả bối cảnh hoạt động của một ứng dụng.

public class Context { private int data; public Context () { this.data = 0; } public void increase () { this.data += 1; } public void decrease () { this.data -= 1; } public int getData () { return this.data; } } // -- end Context

Lúc này chúng ta có một object Context sử dụng chung bởi tất cả các thành phần trong ứng dụng. Giả sử, chúng ta có thread A thực hiện tăng giá trị và truy xuất, và đồng thời thread B thực hiện giảm giá trị và truy xuất. Lúc này, kết quả vận hành có thể sẽ diễn ra như sau:

  • thread A truy xuất giá trị của data=0 và thực hiện tăng giá trị thành 1.
  • thread B truy xuất giá trị của data=0 và thực hiện giảm giá trị thành -1.
  • thread A ghi giá trị trở lại bộ nhớ data=1.
  • thread B ghi giá trị trở lại bộ nhớ data=-1.
  • thread A truy xuất giá trị và in ra data=-1.
  • thread B truy xuất giá trị và in ra data=-1.

Như vậy là kết quả mà chúng ta dự kiến cho logic hoạt động của thread A đã sai lệch. Thay vì in ra giá trị là 1 thì câu lệnh in của thread A lại in ra -1. Và cấu trúc dữ liệu Context mà chúng ta định nghĩa lúc này được gọi là non-threadsafe - có nghĩa là không đảm bảo an toàn khi sử dụng trong môi trường đa nhiệm. Và để đơn giản hóa giải pháp ở đây thì Java có cung cấp một từ khóa synchronized để có thể sử dụng cho các phương thức và các khối lệnh.

public class SynchronizedContext { private int data; public Context () { this.data = 0; } public synchronized void increase () { this.data += 1; } public synchronized void decrease () { this.data -= 1; } public synchronized int getData () { return this.data; } } // -- end Context

Và đây chính là cách mà Java tạo ra các phiên bản thread-safe của các cấu trúc dữ liệu. Khi có nhiều thread đồng thời gọi một phương thức synchronized, giả sử đầu tiên .increase() được thread A gọi sẽ được xử lý trước như trên. Thì tất cả các lời gọi phương thức khác của SynchronizedContext đều sẽ tạm dừng để chờ .increase() được thực thi xong trên thread A.

Sau khi .increase() được thực thi xong, JVM sẽ tiếp tục kiểm tra thread-id của thread A trong số các lời gọi phương thức còn lại và tìm thấy .getData() để in ra và sẽ tiếp tục ưu tiên thread A xử lý tương tác này. Và kết quả hoạt động mà chúng ta có ở đây sẽ là:

  • thread A truy xuất giá trị của data=0 và thực hiện tăng giá trị thành 1.
  • thread A ghi giá trị trở lại bộ nhớ data=1.
  • thread A truy xuất giá trị và in ra data=1.
  • thread B truy xuất giá trị của data=1 và thực hiện giảm giá trị thành 0.
  • thread B ghi giá trị trở lại bộ nhớ data=0.
  • thread B truy xuất giá trị và in ra data=0.

Trong trường hợp cần chuyển đổi một cấu trúc dữ liệu non-threadsafe thành kiểu synchronized, chúng ta có thể sử dụng các phương thức synchronized của java.util.Collections

Đây có lẽ đã là điểm dừng phù hợp của chủ đề Concurrency Programming trong Sub-Series mang tính chất giới thiệu ngôn ngữ. Tiếp theo chúng ta đã có thể suy nghĩ tới việc tự viết code để quản lý một database đơn giản cho ứng dụng muốn xây dựng. Đối với Sub-Series này thì mình đã cân nhắc khá nhiều và quyết định chọn chủ đề cho mini project nghiệm thu kiến thức là ứng dụng quản lý một cửa hàng bán và cho thuê sách.

Cơ sở dữ liệu sẽ ở dạng mở và lưu trữ trong các tệp .xml và vì vậy nên nếu như bạn chọn sử dụng một kiểu CSDL nào đó khác như SQL thì có thể tham khảo hướng dẫn của W3schools hoặc TutorialsPoint và bỏ qua bài viết sau.

[Object-Oriented + Java] Bài viết #9 - XML DOM Parser

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 51

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 31

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 27

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 47

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 266

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 35