CodeGym /Blog Java /Ngẫu nhiên /Cùng nhau tốt hơn: Java và lớp Thread. Phần VI — Bắn đi!
John Squirrels
Mức độ
San Francisco

Cùng nhau tốt hơn: Java và lớp Thread. Phần VI — Bắn đi!

Xuất bản trong nhóm

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 runthì 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.Semaphorelớ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.CountDownLatchlớ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ó, CyclicBarrierlà một rào cản "có thể tái sử dụng". Chúng ta sẽ cần nhập java.util.concurrent.CyclicBarrierlớ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 awaitphươ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 CountDownLatchkhô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 Exchangerlà 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.Phaserlớ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ó Phaserbằ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!  - 3Nhiề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 đó ExecutorServicelà một kiểu "nâng cấp" của Executor. VàExecutornhằ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, Executorchỉ 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
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION