JavaRush /Blog Java /Random-VI /Bạn không thể làm hỏng Java bằng một Chủ đề: Phần VI - Tớ...
Viacheslav
Mức độ

Bạn không thể làm hỏng Java bằng một Chủ đề: Phần VI - Tới hàng rào!

Xuất bản trong nhóm

Giới thiệu

Stream là một điều thú vị. Trong các bài đánh giá trước, 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 chúng ta còn có thể làm được những điều thú vị nào nữa nhé. Tại thời điểm này, chúng tôi biết rất nhiều. Ví dụ: từ “ Bạn không thể làm hỏng Java bằng một chủ đề: Phần I - Chủ đề ”, chúng ta biết rằng một chủ đề là một Chủ đề. Chúng tôi biết rằng một chủ đề đang thực hiện một số nhiệm vụ. Nếu chúng ta muốn tác vụ của mình có thể chạy ( run), thì chúng ta phải chỉ định luồng là một Runnable. Bạn không thể làm hỏng Java bằng một Chủ đề: Phần VI - Tới hàng rào!  - 1Để ghi nhớ, chúng ta có thể sử dụng Trình biên dịch trực tuyến Java 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 khái niệm như khóa. Chúng tôi đã đọc về nó trong “ Bạn không thể làm hỏng Java bằng một chủ đề: Phần II - Đồng bộ hóa ”. Một luồng có thể chiếm một khóa và sau đó một luồng khác cố gắng chiếm khóa sẽ bị buộc phải đợi 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.

Ngữ nghĩa

Phương tiện đơn giản nhất để kiểm soát số lượng luồng có thể hoạt động đồng thời là semaphore. Giống như trên đường sắt. Đèn xanh đang bật - bạn có thể. Đèn đỏ đã bật - chúng tôi đang đợi. Chúng ta mong đợi điều gì từ một semaphore? Quyền. Giấy phép bằng tiếng Anh - giấy phép. Để có được sự cho phép, bạn cần phải có được nó, bằng tiếng Anh sẽ có được. Và khi không cần đến sự cho phép nữa thì chúng ta phải cho đi, tức là thả ra hoặc bỏ đi, nói tiếng Anh là sẽ được thả ra. Hãy xem nó hoạt động như thế nào. Chúng ta sẽ cần nhập lớp java.util.concurrent.Semaphore. 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ư chúng ta có thể thấy, sau khi ghi nhớ các từ tiếng Anh, chúng ta hiểu cách hoạt động của semaphore. Thật thú vị, điều kiện chính là “tài khoản” semaphore phải có số lượng giấy phép dương. Vì vậy, bạn có thể bắt đầu nó bằng một điểm trừ. Và bạn có thể yêu cầu (có được) nhiều hơn 1.

Đếm XuốngChốt

Cơ chế tiếp theo là CountDownLatch. CountDown trong tiếng Anh là đếm ngược, còn Latch là chốt hoặc chốt. Tức là, nếu chúng ta dịch nó thì đây là một chốt có đếm ngược. Ở đây chúng ta cần nhập lớp thích hợp java.util.concurrent.CountDownLatch. Nó giống như một cuộc đua hay một cuộc đua mà mọi người tập trung ở vạch xuất phát và khi mọi người đã sẵn sàng thì được phép và mọi người đều xuất phát cùng một lúc. 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();
 	}
}
chờ đợi trong tiếng Anh - mong đợi. Tức là chúng ta nói chuyện trước countDown. Như Google Translator nói, đếm ngược là “hành động đếm các chữ số theo thứ tự ngược về 0”, tức là thực hiện hành động đếm ngược, mục đích của việc này là đếm về 0. Và sau đó chúng ta nói await- nghĩa là đợi cho đến khi giá trị bộ đếm trở thành 0. Điều thú vị là một bộ đếm như vậy chỉ dùng một lần. Như đã nói trong JavaDoc - "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", nghĩa là, nếu bạn cần đếm có thể sử dụng lại, bạn cần sử dụng một tùy chọn khác, được gọi là CyclicBarrier.

Rào cản tuần hoàn

Như tên cho thấy, CyclicBarriernó là một rào cản mang tính chu kỳ. Chúng ta sẽ cần nhập lớp java.util.concurrent.CyclicBarrier. Hãy xem một ví dụ:
public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("На старт!");
	CyclicBarrier berrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			berrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + berrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
Như bạn có thể thấy, luồng đang thực thi await, nghĩa là đang chờ. Trong trường hợp này, giá trị của rào cản giảm. Rào cản được coi là bị phá vỡ ( berrier.isBroken()) khi đếm ngược về 0. Để thiết lập lại rào cản, bạn cần gọi berrier.reset(), cái bị thiếu trong CountDownLatch.

trao đổi

Biện pháp khắc phục tiếp theo là Exchanger. Trao đổi từ tiếng Anh được dịch là trao đổi hoặc trao đổi. A Exchangerlà vật trao đổi, tức là thứ mà qua đó chúng trao đổi. Hãy xem một ví dụ đơn giản:
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() + " обменялся с " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
Ở đây chúng tôi khởi chạy hai chủ đề. Mỗi người trong số họ thực thi phương thức trao đổi và đợi một luồng khác cũng thực thi phương thức trao đổi. Do đó, các luồng sẽ trao đổi các đối số được truyền với nhau. Điều thú vị. Cô ấy không nhắc nhở bạn điều gì à? Và anh ấy nhắc nhở SynchronousQueue, điều nằm ở trung tâm của cachedThreadPool'a. Để 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 bằng cách khởi chạy một luồng mới, luồng này sẽ chuyển sang chế độ chờ, bởi vì hàng đợi sẽ trống. Và sau đó mainchuỗi sẽ xếp hàng văn bản “Tin nhắn”. Đồng thời, nó sẽ dừng trong khoảng thời gian cần thiết cho đến khi nhận được phần tử văn bản này từ hàng đợi. Về chủ đề này, bạn cũng có thể đọc " SynchronousQueue Vs Exchanger ".

máy pha

Và cuối cùng, điều ngọt ngào nhất - Phaser. Chúng ta sẽ cần nhập lớp java.util.concurrent.Phaser. Hãy xem một ví dụ đơn giản:
public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // Вызывая метод register, мы регистрируем текущий поток (main) How участника
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // Через 3 секунды прибываем к барьеру и снимаемся регистрацию. Кол-во прибывших = кол-во регистраций = пуск
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // Говорим, что будет +1 участник на Phaser
        phaser.register();
        // Запускаем новый поток
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); //threads register arrival to the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
Ví dụ cho thấy rào cản khi sử dụng Phaser'a sẽ bị phá vỡ khi số lượng đăng ký trùng với số lượng người đến rào chắn. Bạn có thể tìm hiểu thêm Phasertrong bài viết từ trung tâm " Bộ đồng bộ hóa Phaser mới ".

Kết quả

Như bạn có thể thấy từ các ví dụ, có nhiều cách khác nhau để đồng bộ hóa các luồng. Trước đó tôi đã cố nhớ đôi điều về đa luồng, tôi hy vọng những phần trước hữu ích. Họ nói rằng con đường dẫn đến đa luồng bắt đầu từ cuốn sách "Thực hành đồng thời Java". Mặc dù nó ra mắt vào năm 2006 nhưng mọi người đều phản hồi rằng cuốn sách này khá cơ bản và vẫn có sức ảnh hưởng lớn. Ví dụ: bạn có thể đọc các cuộc thảo luận ở đây: " Thực tế đồng thời Java có còn hiệu lực không? ". Việc đọc các liên kết từ cuộc thảo luận cũng rất hữu ích. Ví dụ: có một liên kết đến cuốn sách " Nhà phát triển Java có căn cứ tốt ", trong đó đáng chú ý đến " Chương 4. Đồng thời hiện đại ". Có một bài đánh giá khác về cùng chủ đề: “ Liệu đồng tiền Java trong thực tế có còn phù hợp trong kỷ nguyên java 8 không ”. Nó cũng có những lời khuyên về những gì bạn nên đọc để thực sự hiểu chủ đề. Sau đó, bạn có thể xem kỹ hơn một cuốn sách tuyệt vời như " Bài kiểm tra thực hành dành cho lập trình viên OCA OCP JavaSE 8 ". Chúng tôi quan tâm đến phần thứ hai, đó là OCP. Và có những bài kiểm tra trong "∫". Cuốn sách này bao gồm cả câu hỏi và câu trả lời kèm theo lời giải thích. Ví dụ: Bạn không thể làm hỏng Java bằng một Chủ đề: Phần VI - Tới hàng rào!  - 3Nhiều người có thể bắt đầu nói rằng đây chỉ là một cách ghi nhớ các phương pháp khác. Một mặt, có. Mặt khác, câu hỏi này có thể được trả lời bằng cách nhớ rằng ExecutorServiceđây là một loại “nâng cấp” Executor. Và Executornó chỉ nhằm mục đích ẩn phương thức tạo luồng chứ không phải là cách chính để thực thi chúng, tức là chạy trong một luồng mới Runnable. Vì vậy, execute(Callable)không, bởi vì họ chỉ đơn giản là thêm các phương thức ExecutorServicecó thể trả về . Như bạn có thể thấy, chúng ta có thể ghi nhớ một danh sách các phương thức, nhưng sẽ dễ đoán hơn nhiều nếu chúng ta biết bản chất của các lớp. Vâng, một số tài liệu bổ sung về chủ đề này: ExecutorsubmitFuture #Viacheslav
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION