Giới thiệu
Chủ đề là một điều thú vị. Trong các bài đánh giá trước đây, chúng tôi đã xem xét một số công cụ có sẵn để triển khai đa luồng. Hãy xem những điều thú vị khác chúng ta có thể làm. Tại thời điểm này, chúng tôi biết rất nhiều. Ví dụ, từ "
Better together: Java and the Thread class. Part I — Threads of execution ", chúng ta biết rằng lớp Thread đại diện cho một luồng thực thi. Chúng tôi biết rằng một chủ đề thực hiện một số nhiệm vụ. Nếu chúng ta muốn các nhiệm vụ của mình có thể thực hiện được
run
thì chúng ta phải đánh dấu luồng bằng
Runnable
.
![Cùng nhau tốt hơn: Java và lớp Thread. Phần VI — Bắn đi! - 1]()
Để ghi nhớ, chúng ta có thể sử dụng
Trình biên dịch Java trực tuyến Tutorialspoint :
public static void main(String[] args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
Chúng tôi cũng biết rằng chúng tôi có một thứ gọi là khóa. Chúng ta đã học về điều này trong "
Better together: Java and the Thread class. Part II—Synchronization . Nếu một luồng lấy được khóa, thì một luồng khác đang cố lấy khóa sẽ buộc phải chờ khóa được giải phóng:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
Tôi nghĩ đã đến lúc nói về những điều thú vị khác mà chúng ta có thể làm.
đèn hiệu
Cách đơn giản nhất để kiểm soát số lượng luồng có thể chạy đồng thời là một semaphore. Nó giống như một tín hiệu đường sắt. Màu xanh lá cây có nghĩa là tiếp tục. Màu đỏ có nghĩa là chờ đợi. Đợi những gì từ semaphore? Truy cập. Để có được quyền truy cập, chúng ta phải có được nó. Và khi quyền truy cập không còn cần thiết nữa, chúng ta phải cho đi hoặc giải phóng nó. Hãy xem làm thế nào điều này hoạt động. Chúng ta cần nhập
java.util.concurrent.Semaphore
lớp. Ví dụ:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
Như bạn có thể thấy, các hoạt động này (mua và giải phóng) giúp chúng tôi hiểu cách thức hoạt động của một semaphore. Điều quan trọng nhất là nếu chúng ta muốn có quyền truy cập thì semaphore phải có số lượng giấy phép dương. Số đếm này có thể được khởi tạo thành một số âm. Và chúng tôi có thể yêu cầu (có được) nhiều hơn 1 giấy phép.
Đếm NgượcChốt
Cơ chế tiếp theo là
CountDownLatch
. Không có gì đáng ngạc nhiên, đây là một chốt có đếm ngược. Ở đây chúng ta cần câu lệnh nhập thích hợp cho
java.util.concurrent.CountDownLatch
lớp. Nó giống như một cuộc chạy đua bằng chân, nơi mọi người tập trung tại vạch xuất phát. Và một khi mọi người đã sẵn sàng, mọi người sẽ nhận được tín hiệu bắt đầu cùng một lúc và bắt đầu đồng thời. Ví dụ:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Đầu tiên, trước tiên chúng tôi nói với chốt để
countDown()
. Google định nghĩa đếm ngược là "hành động đếm các chữ số theo thứ tự đảo ngược về 0". Và sau đó chúng tôi nói với chốt để
await()
, tức là đợi cho đến khi bộ đếm trở thành số không. Thật thú vị, đây là bộ đếm một lần. Tài liệu Java cho biết: "Khi các luồng phải đếm ngược liên tục theo cách này, thay vào đó hãy sử dụng CyclicBarrier". Nói cách khác, nếu bạn cần một bộ đếm có thể tái sử dụng, bạn cần một tùy chọn khác:
CyclicBarrier
.
hàng rào tuần hoàn
Như tên của nó,
CyclicBarrier
là một rào cản "có thể tái sử dụng". Chúng ta sẽ cần nhập
java.util.concurrent.CyclicBarrier
lớp. Hãy xem xét một ví dụ:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Như bạn thấy, luồng chạy
await
phương thức, tức là nó đợi. Trong trường hợp này, giá trị hàng rào giảm. Rào cản được coi là bị phá vỡ (
barrier.isBroken()
) khi đếm ngược về 0. Để đặt lại rào cản, bạn cần gọi
reset()
phương thức
CountDownLatch
không có.
trao đổi
Cơ chế tiếp theo là Exchanger. Trong bối cảnh này, Exchange là một điểm đồng bộ hóa nơi mọi thứ thay đổi được trao đổi hoặc hoán đổi. Như bạn mong đợi, an
Exchanger
là một lớp thực hiện trao đổi hoặc trao đổi. Hãy xem ví dụ đơn giản nhất:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
Ở đây chúng tôi bắt đầu hai chủ đề. Mỗi người trong số họ chạy phương thức trao đổi và đợi luồng khác cũng chạy phương thức trao đổi. Khi làm như vậy, các luồng trao đổi các đối số đã truyền. Hấp dẫn. Nó không nhắc nhở bạn về một cái gì đó? Nó gợi nhớ đến
SynchronousQueue
, nằm ở trung tâm của
CachedThreadPool
. Để rõ ràng, đây là một ví dụ:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
Ví dụ cho thấy rằng khi một luồng mới được bắt đầu, nó sẽ chờ vì hàng đợi sẽ trống. Và sau đó luồng chính đặt chuỗi "Tin nhắn" vào hàng đợi. Hơn nữa, nó cũng sẽ dừng cho đến khi nhận được chuỗi này từ hàng đợi. Bạn cũng có thể đọc "
SynchronousQueue vs Exchanger " để tìm hiểu thêm về chủ đề này.
phaser
Chúng tôi đã để dành điều tốt nhất cho lần cuối —
Phaser
. Chúng ta sẽ cần nhập
java.util.concurrent.Phaser
lớp. Hãy xem xét một ví dụ đơn giản:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
// By calling the register method, we register the current (main) thread as a party
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
// After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
// We indicate that there will be a +1 party on the Phaser
phaser.register();
// Start a new thread
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
System.out.println(name + " after passing barrier");
}).start();
}
Ví dụ minh họa rằng khi sử dụng
Phaser
, hàng rào sẽ bị phá vỡ khi số lượng đăng ký khớp với số lượng người đến hàng rào. Bạn có thể làm quen với nó
Phaser
bằng cách đọc
bài viết này của GeekforGeek .
Bản tóm tắt
Như bạn có thể thấy từ các ví dụ này, có nhiều cách khác nhau để đồng bộ hóa các luồng. Trước đó, tôi đã cố gắng nhớ lại các khía cạnh của đa luồng. Tôi hy vọng các phần trước trong loạt bài này hữu ích. Một số người nói rằng con đường dẫn đến đa luồng bắt đầu với cuốn sách "Java Concurrency in Practice". Mặc dù nó đã được phát hành vào năm 2006, nhưng mọi người nói rằng cuốn sách khá cơ bản và vẫn còn phù hợp cho đến ngày nay. Ví dụ: bạn có thể đọc cuộc thảo luận tại đây:
"Thực hành đồng thời Java" có còn hiệu lực không? . Nó cũng hữu ích để đọc các liên kết trong cuộc thảo luận. Ví dụ: có một liên kết đến cuốn sách
The Well-Grounded Java Developer và chúng tôi sẽ đề cập cụ thể đến
Chương 4. Đồng thời hiện đại . Ngoài ra còn có một đánh giá toàn bộ về chủ đề này:
"Thực hành đồng thời Java" có còn hiệu lực trong Kỷ nguyên Java 8 không? Bài viết đó cũng đưa ra các gợi ý về những gì khác cần đọc để thực sự hiểu chủ đề này. Sau đó, bạn có thể xem một cuốn sách hay như
OCA/OCP Java SE 8 Programmer Practice Tests . Chúng tôi quan tâm đến từ viết tắt thứ hai: OCP (Oracle Certified Professional). Bạn sẽ tìm thấy các bài kiểm tra trong "Chương 20: Đồng thời Java". Cuốn sách này có cả câu hỏi và câu trả lời với lời giải thích. Ví dụ:
![Cùng nhau tốt hơn: Java và lớp Thread. Phần VI — Bắn đi! - 3]()
Nhiều người có thể bắt đầu nói rằng câu hỏi này là một ví dụ khác về việc ghi nhớ các phương pháp. Một mặt, vâng. Mặt khác, bạn có thể trả lời câu hỏi này bằng cách nhớ lại rằng đó
ExecutorService
là một kiểu "nâng cấp" của
Executor
. Và
Executor
nhằm mục đích đơn giản là ẩn cách các luồng được tạo, nhưng nó không phải là cách chính để thực thi chúng, nghĩa là bắt đầu một
Runnable
đối tượng trên một luồng mới. Đó là lý do tại sao không có
execute(Callable)
— bởi vì trong
ExecutorService
,
Executor
chỉ cần thêm
submit()
các phương thức có thể trả về một
Future
đối tượng. Tất nhiên, chúng ta có thể ghi nhớ một danh sách các phương pháp, nhưng sẽ dễ dàng hơn nhiều để đưa ra câu trả lời dựa trên kiến thức của chúng ta về bản chất của chính các lớp. Và đây là một số tài liệu bổ sung về chủ đề này:
Kết hợp tốt hơn: Java và lớp Thread. Phần I - Các luồng thực thi Cùng nhau tốt hơn: Java và lớp Thread. Phần II — Đồng bộ hóa Tốt hơn khi kết hợp với nhau: Java và lớp Thread. Phần III — Tương tác tốt hơn khi kết hợp với nhau: Java và lớp Thread. Phần IV - Có thể gọi được, Tương lai và bạn bè Tốt hơn cùng nhau: Java và lớp Chủ đề. Phần V — Executor, ThreadPool, Fork/Join
GO TO FULL VERSION