| Điểm quan trọng | Mô tả |
|---|---|
| Multithreading | Multithreading là khả năng của 1 chương trình thực thi nhiều luồng đồng thời, điều này cho phép các phần của chương trình chạy độc lập và song song với nhau, tận dụng tối đa tài nguyên của hệ thống và cải thiện hiệu xuất. |
| Lớp Thread / Giao diện Runnable | Multithreading thường được triển khai thông qua lớp Thread. Bạn có thể mở rộng lớp này và định nghĩa phương thức run() để chỉ định công việc của luồng. Ngoài ra, bạn cũng có thể triển khai giao diện Runnable. Điều này cho phép chia sẻ một đối tượng giữa nhiều luồng mà không cần kế thừa từ lớp Thread, giảm thiểu sự phụ thuộc vào kế thừa. |
| Tạo và Quản lý Luồng | Java cung cấp các phương thức để tạo và quản lý luồng, bao gồm start() để bắt đầu thực thi một luồng, join() để chờ đợi một luồng khác hoàn thành, và interrupt() để gửi tín hiệu gián đoạn đến một luồng. |
| Đồng bộ hóa | Đảm bảo chỉ một luồng có thể truy cập vào một tài nguyên cùng một lúc. Java cung cấp từ khóa synchronized và các phương thức đồng bộ hóa để thực hiện điều này. |
| Quản lý Tài nguyên | Multithreading đặt ra nhiều thách thức như xung đột dữ liệu và deadlock. Java cung cấp locks, conditions và các cấu trúc dữ liệu đồng bộ để giải quyết vấn đề này. |
| ThreadPool | Java cung cấp ExecutorService và ThreadPoolExecutor để tối ưu hóa việc sử dụng tài nguyên và cải thiện hiệu suất. Cho phép bạn quản lý một nhóm luồng tái sử dụng. |
1. Thread
Luồng (thread) là một đơn vị nhỏ nhất của công việc thực thi, cho phép các phần của một ứng dụng chạy đồng thời và độc lập với nhau, tận dụng tài nguyên của hệ thống một cách hiệu quả.
Dưới đây là một bảng mô tả vòng đời của một luồng trong Java:
| Trạng thái | Mô tả |
|---|---|
| New | Luồng được tạo ra nhưng chưa được bắt đầu thực thi. |
| Runnable | Luồng đang chờ để được thực thi trong bộ lập lịch của JVM. |
| Blocked | Luồng bị chặn khi cố gắng thực hiện một hoạt động mà không thể tiếp tục. |
| Waiting | Luồng đang chờ đợi cho một tín hiệu từ một luồng khác để tiếp tục thực thi. |
| Timed Waiting | Tương tự như trạng thái Waiting, nhưng với một khoảng thời gian được xác định. |
| Terminated | Luồng đã hoàn thành công việc của mình hoặc đã bị hủy bỏ và không thể thực thi nữa. |
Có hai cách để tạo ra một thread:
- Bởi extends lớp Thread
- Bởi implements Runnable interface.
Dưới đây là một ví dụ đơn giản về cách tạo và thực thi một luồng trong Java bằng cách mở rộng lớp Thread hoặc triển khai giao diện Runnable:
- Sử dụng lớp Thread:
- Sử dụng giao diện Runnable:
class MyThread extends Thread {
public void run() {
System.out.println("Luồng đang thực thi...");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // Bắt đầu thực thi luồng
}
}class MyRunnable implements Runnable {
public void run() {
System.out.println("Luồng đang thực thi...");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable); // Tạo một luồng mới
thread.start(); // Bắt đầu thực thi luồng
}
}Cả hai ví dụ trên đều tạo một luồng mới và thực thi một phương thức run() được định nghĩa trong lớp hoặc giao diện. Khi phương thức start() được gọi, JVM sẽ tự động gọi phương thức run() của luồng, bắt đầu thực thi công việc được định nghĩa trong đó.
Lớp Thread cung cấp các constructor và phương thức để tạo và thực hiện các hoạt động trên một thread.
Dưới đây là bảng cho các phương thức thường được sử dụng của lớp Thread:
| Phương thức | Mô tả |
|---|---|
public void run() | Được sử dụng để thực hiện hành động cho một thread. |
public void start() | Bắt đầu thực hiện thread. JVM gọi phương thức run() trên thread. |
public void sleep(long miliseconds) | Làm cho thread hiện tại tạm ngừng thực thi cho số mili giây quy định. |
public void join() | Đợi cho một thread chết. |
public void join(long miliseconds) | Đợi cho một thread chết với các mili giây quy định. |
public int getPriority() | Trả về mức độ ưu tiên của thread. |
public int setPriority(int priority) | Thay đổi mức độ ưu tiên của thread. |
public String getName() | Trả về tên của thread. |
public void setName(String name) | Thay đổi tên của thread. |
public Thread currentThread() | Trả về tham chiếu của thread đang được thi hành. |
public int getId() | Trả về id của thread. |
public Thread.State getState() | Trả về trạng thái của thread. |
public boolean isAlive() | Kiểm tra nếu thread còn sống. |
public void yield() | Làm cho các đối tượng thread đang thực thi tạm thời tạm dừng và cho phép các thread khác thực thi. |
public void suspend() | Được sử dụng để hoãn lại các thread (không dùng nữa). |
public void resume() | Được sử dụng để tiếp tục các thread đang bị hoãn (không dùng nữa). |
public void stop() | Được sử dụng để dừng thread (không dùng nữa). |
public boolean isDaemon() | Kiểm tra nếu thread là một luồng hiểm. |
public void setDaemon(boolean b) | Đánh dấu thread là luồng hiểm hoặc luồng người dùng. |
public void interrupt() | Ngắt thread. |
public boolean isInterrupted() | Kiểm tra nếu thread đã bị ngắt. |
public static boolean interrupted() | Kiểm tra nếu thread hiện tại đã bị ngắt. |
3. Đồng bộ hóa
1 2 3 4 5 6 7 | public class Counter { int count=0; public void tang() { count=count+1; }} |
"Giả sử rằng, tại cùng 1 thời điểm có 2 luồng cùng lúc gọi phương thức tang() trên 1 đối tượng thuộc lớp Counter.
Như vậy, cùng 1 lúc, 2 luồng cùng lấy ra được giá trị count hiện tại là 0, và cùng lúc cộng thêm 1 vào giá trị count này thành 1, sau đó cùng ghi giá trị mới cộng lại được là 1 lên RAM.
Nếu thực sự như vậy, sau khi cả 2 luồng thực hiện công việc thì count có giá trị 1. Tuy nhiên, 2 luồng cùng tăng giá trị count thì count phải có giá trị 2 mới đúng là việc chúng ta mong muốn
=> Việc sắp xếp thứ tự truy xuất đối tượng lúc này là thật sự cần thiết khi các luồng có dùng chung dữ liệu.
Đồng bộ hóa (synchronized) chính là việc xắp xếp thứ tự các luồng khi truy xuất vào cùng đối tượng sao cho không có sự xung đột dữ liệu, Java cung cấp từ khóa synchronized và các phương thức đồng bộ hóa để thực hiện điều này."
Ví dụ khi không đồng bộ :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | class Table { void printTable(int n) {// method không synchronized for (int i = 1; i <= 5; i++) { System.out.println(n * i); try { Thread.sleep(400); } catch (Exception e) { System.out.println(e); } } }}class MyThread1 extends Thread { Table t; MyThread1(Table t) { this.t = t; } public void run() { t.printTable(5); }}class MyThread2 extends Thread { Table t; MyThread2(Table t) { this.t = t; } public void run() { t.printTable(100); }}public class TestSynchronization1 { public static void main(String args[]) { Table obj = new Table();// tao mot object MyThread1 t1 = new MyThread1(obj); MyThread2 t2 = new MyThread2(obj); t1.start(); t2.start(); }} |
Output:
5 100 200 10 300 15 400 20 25 500
Ví dụ khi đồng bộ :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | class Table { synchronized void printTable(int n) { // synchronized method for (int i = 1; i <= 5; i++) { System.out.println(n * i); try { Thread.sleep(400); } catch (Exception e) { System.out.println(e); } } }}class MyThread1 extends Thread { Table t; MyThread1(Table t) { this.t = t; } public void run() { t.printTable(5); }}class MyThread2 extends Thread { Table t; MyThread2(Table t) { this.t = t; } public void run() { t.printTable(100); }}public class TestSynchronization2 { public static void main(String args[]) { Table obj = new Table();// tao mot object MyThread1 t1 = new MyThread1(obj); MyThread2 t2 = new MyThread2(obj); t1.start(); t2.start(); }} |
Output:
5 10 15 20 25 100 200 300 400 500
4. Quản lý tài nguyên
| Thuật ngữ | Mô tả |
|---|---|
| Monitor (lock) | Một cơ chế được sử dụng để đảm bảo đồng bộ hóa giữa các luồng khi chúng chia sẻ tài nguyên hoặc dữ liệu. Mỗi đối tượng trong Java được gắn với một monitor để kiểm soát quyền truy cập vào đối tượng đó bởi các luồng. Khi một luồng đã có monitor của một đối tượng, nó có thể thực hiện các hoạt động như đọc hoặc cập nhật dữ liệu trong đối tượng đó. Các luồng khác cần phải chờ đợi cho đến khi monitor của đối tượng đó được giải phóng trước khi họ có thể tiếp tục thực thi các hoạt động trên đối tượng đó. |
| wait() | Khi một luồng gọi wait() trên một đối tượng, nó giải phóng monitor và chờ đợi cho đến khi một luồng khác gọi notify() hoặc notifyAll() trên cùng một đối tượng để thông báo rằng điều kiện đã thay đổi. Khi đó, luồng đang chờ sẽ được thức tỉnh và có thể tiếp tục thực thi. |
| notify() | Được gọi trên một đối tượng và thông báo cho một luồng đang chờ (được chọn ngẫu nhiên) rằng điều kiện đã thay đổi và nó có thể tiếp tục thực thi. |
| notifyAll() | Được gọi trên một đối tượng và thông báo cho tất cả các luồng đang chờ rằng điều kiện đã thay đổi và chúng có thể tiếp tục thực thi. |
| Sử dụng | Cần được gọi trong một khối synchronized trên cùng một đối tượng mà luồng đang chờ hoặc được thông báo. Nếu không, có nguy cơ xảy ra ngoại lệ IllegalMonitorStateException. |
Ví dụ:
public class WaitNotifyExample {
private final Object lock = new Object();
private boolean condition = false;
public void waitForCondition() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait(); // Chờ cho đến khi condition thay đổi và được thông báo
}
// Tiếp tục thực hiện khi condition = true
System.out.println("Đã nhận được thông báo!");
}
}
public void notifyCondition() {
synchronized (lock) {
condition = true; // Đổi trạng thái condition
lock.notify(); // Thông báo cho một luồng đang chờ
// lock.notifyAll(); // Thông báo cho tất cả các luồng đang chờ
}
}
public static void main(String[] args) {
WaitNotifyExample example = new WaitNotifyExample();
Thread waitingThread = new Thread(() -> {
try {
example.waitForCondition();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread notifyingThread = new Thread(() -> {
example.notifyCondition();
});
waitingThread.start();
notifyingThread.start();
}
}Trong ví dụ này, waitingThread sẽ chờ cho đến khi notifyCondition() được gọi và thay đổi trạng thái của condition. Khi đó, waitingThread sẽ nhận được thông báo và tiếp tục thực thi.
Dưới đây là một số cách phổ biến để quản lý tài nguyên trong multithreading:
| Cách quản lý tài nguyên | Mô tả |
|---|---|
| Locks và Mutexes | Đồng bộ hóa truy cập vào tài nguyên chia sẻ giữa các luồng bằng cách chỉ cho phép một luồng truy cập vào tài nguyên vào một thời điểm nhất định. |
| Semaphore | Giới hạn số lượng luồng được phép truy cập vào một tài nguyên cùng một lúc, hữu ích khi cần kiểm soát số lượng luồng thực thi đồng thời. |
| Cấu trúc dữ liệu Thread-Safe | Sử dụng các cấu trúc dữ liệu được thiết kế để làm việc an toàn trong môi trường đa luồng, như ConcurrentHashMap trong Java. |
| Atomic Variables | Biến được thiết kế để hỗ trợ hoạt động nguyên tử, giúp tránh lỗi đồng bộ hóa và đảm bảo tính nhất quán của dữ liệu. |
| Quản lý Đóng gói và Truy cập Tài nguyên | Đảm bảo rằng tài nguyên được truy cập chỉ thông qua các phương thức công cộng và được đóng gói trong các lớp độc lập. |
| Giải phóng Tài nguyên đúng cách | Đảm bảo rằng các tài nguyên được giải phóng đúng cách sau khi không còn được sử dụng, tránh deadlock hoặc memory leak. |
| Kiểm soát Thứ tự Thực thi | Sử dụng cơ chế như wait(), notify() hoặc notifyAll() để kiểm soát thứ tự thực thi của các luồng và đảm bảo các luồng chờ đợi tài nguyên được thông báo đúng cách khi tài nguyên có sẵn. |
5. ThreadPoll
Trong Java, một Thread Pool là một tập hợp các luồng sẵn sàng để thực hiện các công việc. Thay vì tạo ra và hủy bỏ các luồng mỗi khi cần thực hiện một công việc, ta có thể sử dụng một Thread Pool để quản lý các luồng này một cách hiệu quả. Thread Pool giúp tăng hiệu suất và giảm tải cho hệ thống bằng cách tái sử dụng các luồng đã được tạo.
Cách hoạt động của một Thread Pool như sau:
| Bước | Mô tả |
|---|---|
| Khởi tạo | Tạo một số lượng cố định hoặc cung cấp động các luồng, khởi tạo và sẵn sàng để thực hiện. |
| Thêm công việc | Gửi công việc đến Thread Pool, quản lý và giao cho một trong các luồng sẵn có. |
| Thực thi công việc | Các luồng thực hiện các công việc, sau khi hoàn thành, trở lại Pool sẵn sàng tiếp theo. |
| Quản lý tài nguyên | Thread Pool quản lý số lượng luồng, giữ chúng sẵn sàng và tự động thêm/giảm tùy tải. |
Khởi tạo
Có 2 cách để khởi tạo ThredPool là sử dụng các phương thứu của 2 lớp : ThreadPoolExecutor và Executors.
| Đặc Điểm | ThreadPoolExecutor | Executors |
|---|---|---|
| Cấu Hình Chi Tiết | Cho phép cấu hình chi tiết về Thread Pool như số lượng luồng, cách xử lý ngoại lệ, thời gian chờ, và cơ chế phân phối công việc. | Cung cấp các phương thức tiện ích để tạo các loại Thread Pool với các cấu hình mặc định phù hợp với nhu cầu cụ thể của bạn. |
| Linh Hoạt | Cho phép tùy chỉnh cao hơn và điều chỉnh các cài đặt chi tiết của Thread Pool. | Cung cấp một cách dễ dàng và tiện lợi để tạo Thread Pool mà không cần phải cấu hình chi tiết. |
| Tùy Chọn Triển Khai | Bạn cần tự tạo một đối tượng ThreadPoolExecutor từng bước và cấu hình nó theo ý muốn. | Executors cung cấp một số phương thức tạo ra các loại Thread Pool phổ biến một cách dễ dàng và tiện lợi. |
| Kiểm Soát Hành Vi | Cho phép bạn kiểm soát và thay đổi hành vi của Thread Pool theo nhu cầu cụ thể của ứng dụng. | Executors cung cấp các cấu hình mặc định phù hợp với nhu cầu cụ thể, giảm bớt sự phức tạp và mã lặp lại trong mã. |
| Phù Hợp Với Nhu Cầu | Phù hợp cho các ứng dụng cần điều chỉnh và tùy chỉnh cao hơn Thread Pool. | Phù hợp cho các ứng dụng chỉ cần sử dụng các loại Thread Pool phổ biến với cấu hình mặc định. |
Đây là bảng mô tả các phương thức tạo Thread Pool trong lớp Executors:
| Phương thức | Mô tả |
|---|---|
newFixedThreadPool(int nThreads) | Tạo một FixedThreadPool với số lượng luồng cố định. Các công việc mới được gửi đến Thread Pool này sẽ được chia sẻ giữa các luồng có sẵn. |
newCachedThreadPool() | Tạo một CachedThreadPool, không giới hạn số lượng luồng. Thread Pool này sẽ tạo ra hoặc hủy bỏ các luồng tùy thuộc vào số lượng công việc cần thực hiện. |
newSingleThreadExecutor() | Tạo một SingleThreadExecutor với một luồng duy nhất. Các công việc sẽ được thực thi theo thứ tự mà chúng được gửi đến. |
newScheduledThreadPool(int corePoolSize) | Tạo một ScheduledThreadPool với số lượng luồng cố định. Thread Pool này được sử dụng cho việc thực thi các công việc theo lịch trình cố định hoặc lặp lại. |
newWorkStealingPool() | Tạo một WorkStealingPool, một loại ThreadPool mới từ Java 8 trở đi. Nó sử dụng một số lượng luồng cố định và thuật toán "work-stealing" để phân phối công việc. |
newSingleThreadScheduledExecutor() | Tạo một SingleThreadScheduledExecutor với một luồng duy nhất, được sử dụng để thực hiện các công việc theo lịch trình với một luồng duy nhất. |
v




Không có nhận xét nào:
Đăng nhận xét