giới thiệu
Xin chào mọi người, tôi là bạn cũ Xiucai của bạn! Những gì chúng tôi mang đến hôm nay là bài viết thứ năm trong loạt bài [Giới thiệu chuyên sâu về đa luồng Java]: giao tiếp giữa các luồng. Nếu thấy hữu ích thì hãy like, còn nếu thấy hay thì hãy theo dõi nhé! Xiucai xin gửi lời cảm ơn tới mọi người! ! ! .
Trong thực tiễn lập trình hiện đại, công nghệ đa luồng là phương tiện chính để cải thiện hiệu suất chạy đồng thời của chương trình và tối ưu hóa việc sử dụng tài nguyên hệ thống. Là ngôn ngữ hỗ trợ đa luồng chính thống, Java không chỉ cung cấp API phong phú để tạo và quản lý các luồng mà quan trọng hơn là nó có cơ chế giao tiếp liên luồng mạnh mẽ được tích hợp sẵn, cho phép nhiều luồng cộng tác hiệu quả và thực thi các tác vụ một cách đồng bộ, từ đó đảm bảo tính nhất quán của dữ liệu và tính ổn định của hệ thống.
Trong quá trình phát triển thực tế, đặc biệt là trong các ứng dụng phía máy chủ, việc xử lý song song đa luồng có thể cải thiện đáng kể tốc độ phản hồi và thông lượng dịch vụ. Tuy nhiên, việc truy cập vào các tài nguyên được chia sẻ trong môi trường đa luồng thường gây ra sự phức tạp, chẳng hạn như điều kiện chạy đua, bế tắc và các vấn đề khác. Để giải quyết những vấn đề này, chúng ta phải thành thạo các phương pháp và công nghệ khác nhau được sử dụng để kiểm soát việc đồng bộ hóa và giao tiếp luồng trong Java.
Phần giới thiệu trước hết giới thiệu một phép ẩn dụ sống động: Hãy tưởng tượng rằng đa luồng giống như nhiều công nhân làm việc cùng nhau trên cùng một bàn làm việc. Để đảm bảo tiến độ công việc có trật tự và sử dụng tài nguyên một cách an toàn, chúng ta cần một phương pháp tương tự như ". semaphore" hoặc Cơ chế "lập lịch" điều phối sự tương tác giữa các nhân viên này. Trong Java, cơ chế phối hợp này được phản ánh trong các khóa đối tượng (tức là các khóa mutex). Giống như chỉ có một hộp công cụ để hoạt động đồng thời, một khóa đối tượng chỉ có thể được giữ bởi một luồng cùng một lúc. Bằng cách đánh dấu các khối mã hoặc phương thức bằng từ khóa được đồng bộ hóa, chúng tôi có thể đảm bảo rằng bất kỳ lúc nào chỉ có một luồng truy cập vào tài nguyên phần quan trọng cụ thể.
Ví dụ: hãy xem xét một tình huống trong đó hai chủ đề sinh viên A và B đang sao chép câu trả lời cho cùng một bài tập mùa hè. Để tránh tình trạng các em mâu thuẫn trong nội dung bài tập do giáo viên sửa câu trả lời giữa chừng, chúng ta có thể thêm khóa đối tượng vào toàn bộ quá trình sao chép để đảm bảo giáo viên hoàn thành việc sửa đổi trước khi học sinh bắt đầu sao chép, hoặc học sinh sao chép xong trước khi bắt đầu sao chép. Việc sửa đổi nó tùy thuộc vào giáo viên, điều này phản ánh việc thực thi đồng bộ giữa các luồng.
công cộng lớp học Khóa đối tượng {
riêng tư tĩnh cuối cùng Khóa đối tượng = mới Sự vật();
tĩnh lớp học Sinh viênChủ đề dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
đồng bộ (khóa) {
vì (int tôi = 0; 100; i++) {
System.out.println("Học sinh đang chép đáp án" + tôi);
}
}
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) {
Chủ đề sinh viênA = mới Chủ đề(mới Sinh viênThread());
Giáo viên chủ đề = mới Chủ đề(() -> {
đồng bộ (khóa) {
lock.notifyAll();
}
});
sinh viênA.start();
thử { Thread.sleep(1000); } nắm lấy (Ngoại lệ bị gián đoạn e) {}
giáo viên.start();
}
}
Ví dụ trên cho thấy cách sử dụng khóa đối tượng để thực hiện đồng bộ hóa luồng đơn giản, đảm bảo rằng luồng của học sinh bắt đầu sao chép sau khi giáo viên sửa đổi câu trả lời. Tất nhiên, trong các tình huống phức tạp hơn, giao tiếp giữa các luồng cũng bao gồm các phương tiện kỹ thuật đa dạng như cơ chế chờ/thông báo, luồng quy trình, phương thức nối và ThreadLocal. Mỗi phương pháp này đều có những đặc điểm riêng và các kịch bản ứng dụng khác nhau. Sự hiểu biết sâu sắc về nguyên tắc làm việc và cách sử dụng linh hoạt của chúng sẽ giúp các nhà phát triển xây dựng các ứng dụng đa luồng hiệu quả và an toàn. Trong các chương tiếp theo, chúng ta sẽ thảo luận từng cơ chế này và tiết lộ các kịch bản ứng dụng và logic bên trong của chúng thông qua mã ví dụ.
「Khóa và đồng bộ hóa"
Trong lập trình đa luồng Java, các cơ chế khóa và đồng bộ hóa là phương tiện cốt lõi để đảm bảo rằng nhiều luồng truy cập chính xác các tài nguyên được chia sẻ và tránh các vấn đề tương tranh. Trước tiên, hãy hiểu sâu về hai khái niệm này.
Giải thích khái niệm
"Khóa" dựa trên các đối tượng. Mỗi đối tượng Java có thể được liên kết với một khóa nội tại, còn được gọi là "khóa đối tượng". Khi một luồng cố gắng truy cập vào một khối mã yêu cầu đồng bộ hóa, trước tiên nó phải có được khóa đối tượng liên quan. Nếu khóa đã được giữ bởi một luồng khác thì luồng hiện tại phải đợi cho đến khi khóa được giải phóng. Mối quan hệ một đối một này giống như sự độc quyền trong hôn nhân: mỗi lần chỉ có thể "kết hôn" một chủ đề (tức là giữ khóa), còn các chủ đề khác muốn tham gia vào mối quan hệ phải đợi cho đến khi họ "ly hôn" ( tức là mở khóa) để có cơ hội.
"Đồng bộ hóa" nhằm đảm bảo thứ tự thực hiện và tính nhất quán dữ liệu giữa các luồng. Nó được triển khai thông qua từ khóa được đồng bộ hóa để chỉ một luồng có thể thực thi một khối mã hoặc phương thức cụ thể cùng một lúc. Đồng bộ hóa đảm bảo rằng các hoạt động trong phần quan trọng sẽ không được thực thi bởi nhiều luồng cùng lúc, do đó ngăn chặn hiệu quả các xung đột dữ liệu và sự không nhất quán. Ví dụ: khi hai chủ đề học sinh A và B đang sao chép đáp án của cùng một bài tập hè, cơ chế đồng bộ sẽ đảm bảo rằng sau khi giáo viên sửa đáp án, tất cả học sinh sẽ thấy phiên bản mới nhất của đáp án thay vì phiên bản cũ.
ví dụ về mã
Dưới đây là một ví dụ đơn giản về việc sử dụng khóa đối tượng để đồng bộ hóa luồng. Trong ví dụ này, chúng tôi muốn luồng A hoàn thành nhiệm vụ của nó trước khi bắt đầu luồng B để đảm bảo rằng chúng thực thi theo thứ tự.
công cộng lớp học Đối tượngKhóaVí dụ {
riêng tư tĩnh cuối cùng Khóa đối tượng = mới Sự vật();
tĩnh lớp học chủ đềA dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
đồng bộ (khóa) {
vì (int tôi = 0; 100; i++) {
System.out.println("Chủ đề A đang thực hiện nhiệm vụ" + tôi);
}
lock.notify();
}
}
}
tĩnh lớp học Chủ đềB dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
đồng bộ (khóa) {
thử {
lock.wait();
} nắm lấy (Ngoại lệ bị gián đoạn e) {
e.printStackTrace();
}
vì (int tôi = 0; 100; i++) {
System.out.println("Chủ đề B hiện đang thực hiện nhiệm vụ" + tôi);
}
}
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) ném Ngoại lệ bị gián đoạn {
Chủ đề chủ đềA = mới Chủ đề(mới Chủ đềA());
threadA.start();
Thread.sleep(10);
Chủ đề chủ đềB = mới Chủ đề(mới Chủ đềB());
threadB.start();
}
}
Phân tích cơ chế khóa
Trong đoạn mã trên, khối mã được sửa đổi bởi từ khóa được đồng bộ hóa thể hiện quá trình khóa và mở khóa khóa đối tượng. Luồng A trước tiên nhận được khóa và thực hiện in vòng lặp. Sau khi thực hiện xong, nó gọi thông báo () để thông báo cho các luồng đang chờ. Khi luồng B đang chạy, nó cũng cố gắng lấy cùng một khóa, nhưng vì luồng A chưa được giải phóng nên luồng B sẽ bị chặn bên ngoài khối mã được đồng bộ hóa cho đến khi luồng A gọi thông báo() và thoát khỏi khối được đồng bộ hóa để giải phóng khối mã được đồng bộ hóa. khóa. Tại thời điểm này, luồng B có thể lấy được khóa và chuyển từ trạng thái chờ sang trạng thái sẵn sàng để tiếp tục thực thi.
Tóm lại, cơ chế khóa và đồng bộ hóa đóng một vai trò quan trọng trong môi trường đa luồng Java. Chúng hạn chế thứ tự truy cập của các luồng khác nhau đối với các tài nguyên được chia sẻ và đảm bảo tính nhất quán của dữ liệu giữa các luồng và tính chính xác của chương trình. Thông qua việc sử dụng khóa hợp lý, nhà phát triển có thể tránh được các vấn đề tương tranh một cách hiệu quả như điều kiện chạy đua và bế tắc.
「Cơ chế chờ/thông báo"
Nguyên tắc cơ bản
Trong lập trình đa luồng Java, cơ chế chờ/thông báo dựa trên đối tượng là một phương thức đồng bộ hóa nâng cao cho phép một hoặc nhiều luồng vào trạng thái chờ trước khi đáp ứng các điều kiện cụ thể và đánh thức bằng cách gửi thông báo sau khi các luồng khác hoàn thành một thao tác. Những chủ đề chờ đợi này. Cơ chế này chủ yếu dựa vào các phương thức wait(), notification() và notificationAll() do lớp java.lang.Object cung cấp.
-
Chờ đợi()
: Khi luồng hiện tại gọi phương thức này, nó sẽ giải phóng khóa đối tượng hiện đang được giữ và chuyển sang trạng thái chờ không xác định cho đến khi cùng một đối tượng được gọi bởi một luồng khác.
thông báo()
hoặc
thông báoTất cả()
phương pháp thức dậy.
-
thông báo()
: Đánh thức ngẫu nhiên một chuỗi đang chờ giám sát đối tượng (tức là khóa).
-
thông báoTất cả()
: Đánh thức tất cả các chủ đề đang chờ màn hình của đối tượng này.
Khi sử dụng cơ chế chờ/thông báo, bạn phải đảm bảo rằng các phương thức này được gọi trong các phương thức hoặc khối mã được đồng bộ hóa, vì chỉ luồng giữ khóa đối tượng mới có thể thực thi chúng. Ngoài ra, sau khi gọi phương thức wait(), luồng cần lấy lại khóa trước khi tiếp tục thực thi.
Trình diễn ví dụ
Sau đây là ví dụ về cách sử dụng cơ chế chờ/thông báo để điều khiển các luồng in số lần lượt:
công cộng lớp học WaitAndNotifyVí dụ {
riêng tư tĩnh cuối cùng Khóa đối tượng = mới Sự vật();
tĩnh lớp học chủ đềA dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
đồng bộ (khóa) {
vì (int tôi = 0; 5; i++) {
System.out.println("Chủ đềA:" + tôi);
lock.notify();
thử {
nếu như (tôi != 4) {
lock.wait();
}
} nắm lấy (Ngoại lệ bị gián đoạn e) {
e.printStackTrace();
}
}
lock.notify();
}
}
}
tĩnh lớp học Chủ đềB dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
đồng bộ (khóa) {
vì (int tôi = 0; 5; i++) {
thử {
lock.wait();
} nắm lấy (Ngoại lệ bị gián đoạn e) {
e.printStackTrace();
}
System.out.println("Chủ đềB:" + tôi);
lock.notify();
}
}
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) ném Ngoại lệ bị gián đoạn {
Chủ đề chủ đềA = mới Chủ đề(mới Chủ đềA());
threadA.start();
Thread.sleep(100);
Chủ đề chủ đềB = mới Chủ đề(mới Chủ đềB());
threadB.start();
}
}
Chạy đoạn mã trên, bạn sẽ thấy thread A và thread B lần lượt in một dãy số nguyên từ 0 đến 4. Trong ví dụ này, luồng A trước tiên lấy khóa và in số đầu tiên, sau đó gọi notification() để đánh thức luồng B; luồng B ngay lập tức gọi wait() sau khi lấy khóa để tự đưa nó vào trạng thái chờ và luồng A. lấy lại khóa và in số tiếp theo, lặp lại quá trình này cho đến khi hoàn thành năm bản in. Trong suốt quá trình, hai luồng đạt được sự cộng tác và liên lạc chính xác thông qua khóa đối tượng dùng chung và cơ chế chờ/thông báo.
「đường ống thông tin liên lạc"
Định nghĩa và ứng dụng
Trong lập trình đa luồng Java, đường ống là một cơ chế giao tiếp đặc biệt cho phép truyền dữ liệu giữa các luồng thông qua các luồng bộ nhớ. Java.io.PipedWriter và java.io.PipedReader do JDK cung cấp được sử dụng để liên lạc giữa các luồng ký tự, trong khi java.io.PipedOutputStream và java.io.PipedInputStream là các công cụ giao tiếp dựa trên luồng byte. Mô hình giao tiếp đường ống tương tự như ống nước ngoài đời thực, với một luồng đóng vai trò là nhà sản xuất ghi thông tin vào một đầu ống và một luồng khác đóng vai trò là người tiêu dùng đọc thông tin này từ đầu kia của ống.
Giao tiếp đường ống đặc biệt phù hợp với các tình huống trong đó dữ liệu cần được truyền hiệu quả giữa các luồng. Ví dụ: một luồng chịu trách nhiệm tạo dữ liệu và gửi nó đến một luồng khác để xử lý hoặc hiển thị thêm. Cơ chế này đặc biệt phù hợp để tránh các vấn đề đồng bộ hóa do sử dụng các biến dùng chung và đơn giản hóa việc phối hợp giữa các luồng.
Thực hành mã
Sau đây là mã ví dụ sử dụng các đường dẫn Java để liên lạc giữa các luồng:
công cộng lớp học ỐngVí dụ {
tĩnh lớp học Người đọcChủ đề dụng cụ Có thể chạy được {
riêng tư Trình đọc PipedReader;
công cộng Người đọcChủ đề(Trình đọc PipedReader) {
cái này.reader = người đọc;
}
@Ghi đè
công cộng trống rỗng chạy() {
System.out.println("Chủ đề của người đọc đã sẵn sàng để đọc");
thử {
int nhận được;
trong khi ((nhận = reader.read()) != -1) {
System.out.print((ký tự) nhận được);
}
} nắm lấy (IOException e) {
e.printStackTrace();
}
}
}
tĩnh lớp học Nhà vănChủ đề dụng cụ Có thể chạy được {
riêng tư Nhà văn PipedWriter;
công cộng Nhà vănChủ đề(Nhà văn PipedWriter) {
cái này.writer = nhà văn;
}
@Ghi đè
công cộng trống rỗng chạy() {
System.out.println("Chủ đề của người viết đã sẵn sàng để viết");
thử {
nhà văn.write("Xin chào, Thế giới từ đường ống!");
writer.flush();
} nắm lấy (IOException e) {
e.printStackTrace();
} Cuối cùng {
thử {
nhà văn.close();
} nắm lấy (IOException e) {
e.printStackTrace();
}
}
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) ném IOException {
Nhà văn PipedWriter = mới PipedWriter();
Trình đọc PipedReader = mới PipedReader();
reader.connect(nhà văn);
Trình đọc chủ đềThread = mới Chủ đề(mới ReaderThread(người đọc));
Người viết chủ đềThread = mới Chủ đề(mới WriterThread(nhà văn));
readerThread.start();
writerThread.start();
readerThread.join();
writerThread.join();
}
}
Chạy đoạn mã trên, đầu ra sẽ là "Xin chào, Thế giới từ đường ống!". Trong ví dụ này, chúng ta tạo một đường ống ký tự và bắt đầu hai luồng, một luồng chịu trách nhiệm ghi một chuỗi vào đường ống và luồng còn lại chịu trách nhiệm đọc từ đường ống và in nó ra. Vì giao tiếp đường ống là một chiều nên nó đảm bảo rằng dữ liệu chỉ có thể truyền theo hướng đã chỉ định, nhờ đó đạt được giao tiếp có trật tự giữa các luồng.
「Các phương tiện liên lạc khác"
phương pháp tham gia
Phương thức join() là một phương thức thể hiện chính của lớp Thread trong Java và được sử dụng để đồng bộ hóa việc thực thi luồng. Khi một thread gọi phương thức join() của thread khác, thread hiện tại sẽ vào trạng thái chờ cho đến khi thread có tên join() hoàn thành nhiệm vụ của nó và kết thúc. Điều này đặc biệt hữu ích khi bạn cần đảm bảo rằng luồng chính đợi luồng con thực hiện xong trước khi tiếp tục.
Ví dụ: giả sử luồng chính tạo một tác vụ tính toán tốn thời gian và gán nó cho luồng con để thực thi và luồng chính muốn lấy kết quả sau khi luồng con hoàn thành phép tính:
công cộng lớp học Tham giaVí dụ {
tĩnh lớp học Nhiệm vụ chạy dài dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
thử {
System.out.println("Tôi là một chuỗi con và bắt đầu thực hiện các phép tính tốn thời gian...");
Thread.sleep(2000);
int result = performanceComputation();
System.out.println("Tôi là một thread con, quá trình tính toán đã hoàn tất và kết quả là: " + kết quả);
} nắm lấy (Ngoại lệ bị gián đoạn e) {
e.printStackTrace();
}
}
riêng tư int thực hiện tính toán() {
trở lại 42;
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) ném Ngoại lệ bị gián đoạn {
Chủ đề chạy dài = mới Chủ đề(mới LongRunningTask());
longRunning.start();
longRunning.join();
System.out.println("Main chính: Thread con đã hoàn thành, tôi có thể tiếp tục thực hiện các thao tác tiếp theo");
}
}
phương pháp ngủ
sleep() là một phương thức tĩnh được cung cấp bởi lớp Thread để tạm dừng luồng hiện tại trong một khoảng thời gian xác định. Không giống như phương thức wait(), sleep() không giải phóng bất kỳ tài nguyên khóa nào, nghĩa là thread vẫn giữ khóa mà nó có được trong khi ngủ. Ngoài ra, phương thức sleep() không ném ra ngoại lệ InterruptedException trừ khi luồng bị gián đoạn trong khi gọi tới chế độ ngủ().
Mã mẫu:
công cộng lớp học Ví dụ về giấc ngủ {
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) ném Ngoại lệ bị gián đoạn {
Chủ đề chủ đềA = mới Chủ đề(() -> {
vì (int tôi = 0; 5; i++) {
System.out.println("Chủ đề A đang chạy:" + tôi);
thử {
Thread.sleep(1000);
} nắm lấy (Ngoại lệ bị gián đoạn e) {
e.printStackTrace();
}
}
});
threadA.start();
}
}
Lớp ThreadLocal
Lớp ThreadLocal cung cấp một cơ chế lưu trữ liên kết luồng đặc biệt trong đó mỗi luồng có bản sao độc lập riêng. Điều này có nghĩa là ngay cả khi nhiều thread tham chiếu cùng một instance ThreadLocal cùng lúc thì các giá trị mà chúng truy cập và sửa đổi sẽ không ảnh hưởng lẫn nhau.
Sau đây là ví dụ đơn giản sử dụng ThreadLocal để hiển thị cách duy trì thông tin ngữ cảnh độc lập cho từng luồng trong môi trường đa luồng:
công cộng lớp học Chủ đềLocalDemo {
công cộng tĩnh lớp học Chủ đề công nhân mở rộng Chủ đề {
riêng tư cuối cùng Bối cảnh ThreadLocal;
công cộng Chủ đề công nhân(Ngữ cảnh ThreadLocal, Tên chuỗi) {
cái này.context = bối cảnh;
setName(tên);
}
@Ghi đè
công cộng trống rỗng chạy() {
context.set(Thread.currentThread().getName() + ": Giá trị ban đầu");
Chuỗi giá trị mới = "Xử lý bởi" + getName();
bối cảnh.set(newValue);
System.out.println(getName() + " có giá trị riêng của nó: " + bối cảnh.get());
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) {
Bối cảnh ThreadLocal = mới ThreadLocal<>();
WorkerThread worker1 = mới WorkerThread(ngữ cảnh, "Chủ đề-1");
WorkerThread worker2 = mới WorkerThread(ngữ cảnh, "Chủ đề-2");
worker1.start();
worker2.start();
}
}
Trong ví dụ này, WorkerThread kế thừa từ Thread và sử dụng ThreadLocal để chứa thông tin ngữ cảnh cụ thể của luồng. Ngay cả khi cả hai luồng sử dụng cùng một phiên bản ThreadLocal, các biến ngữ cảnh tương ứng của chúng vẫn bị cô lập và mỗi luồng có thể đọc và cập nhật dữ liệu riêng tư của chính nó một cách an toàn.
「cơ chế tín hiệu"
từ khóa dễ bay hơi
Trong lập trình đa luồng Java, từ khóa dễ bay hơi được sử dụng để đảm bảo khả năng hiển thị và thứ tự của các biến. Một biến được khai báo là dễ bay hơi đảm bảo rằng khi một luồng sửa đổi giá trị của biến, tất cả các luồng khác có thể thấy ngay kết quả của việc sửa đổi. Ví dụ: trong ví dụ sau, chúng tôi triển khai mô hình "semaphore" đơn giản bằng cách sử dụng từ khóa dễ bay hơi để điều khiển luồng A và luồng B để in các số luân phiên:
công cộng lớp học Tín hiệuVí dụ {
riêng tư tĩnh bay hơi int tín hiệu = 0;
tĩnh lớp học chủ đềA dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
trong khi (tín hiệu < 5) {
nếu như (tín hiệu % 2 == 0) {
System.out.println("Chủ đề A:" + tín hiệu);
đồng bộ (Ví dụ tín hiệu.lớp học) {
tín hiệu++;
}
}
}
}
}
tĩnh lớp học Chủ đềB dụng cụ Có thể chạy được {
@Ghi đè
công cộng trống rỗng chạy() {
trong khi (tín hiệu < 5) {
nếu như (tín hiệu % 2 == 1) {
System.out.println("Chủ đề B:" + tín hiệu);
đồng bộ (Ví dụ tín hiệu.lớp học) {
tín hiệu = tín hiệu + 1;
}
}
}
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) ném Ngoại lệ bị gián đoạn {
mới Chủ đề(mới ThreadA()).start();
Thread.sleep(100);
mới Chủ đề(mới ThreadB()).start();
}
}
Mặc dù từ khóa dễ bay hơi ở đây đảm bảo khả năng hiển thị các sửa đổi đối với biến tín hiệu, vì signal++ không phải là hoạt động nguyên tử nên vẫn cần có khối đồng bộ hóa được đồng bộ hóa để đảm bảo tính nguyên tử của hoạt động cập nhật.
Lớp ngữ nghĩa
Lớp Semaphore do JDK cung cấp là một triển khai semaphore hoàn chỉnh hơn, có thể được sử dụng để kiểm soát số lượng luồng truy cập các tài nguyên cụ thể cùng lúc, từ đó giải quyết hiệu quả vấn đề kiểm soát đồng thời giữa các luồng. Sau đây là ví dụ về việc sử dụng Semaphore để mô phỏng việc quản lý chỗ đỗ xe trong bãi đỗ xe:
nhập khẩu java.util.concurrent.Semaphore;
công cộng lớp học SemaphoreDemo {
riêng tư cuối cùng Semaphore đậu xeSpaces = mới Semaphore(3);
tĩnh lớp học Xe hơi dụng cụ Có thể chạy được {
riêng tư cuối cùng semaphore semaphore;
công cộng Xe hơi(Semaphore semaphore) {
cái này.semaphore = semaphore;
}
@Ghi đè
công cộng trống rỗng chạy() {
thử {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "đậu");
Thread.sleep(1000);
} nắm lấy (Ngoại lệ bị gián đoạn e) {
e.printStackTrace();
} Cuối cùng {
semaphore.release();
System.out.println(Thread.currentThread().getName() + "bên trái");
}
}
}
công cộng tĩnh trống rỗng chủ yếu(Chuỗi [] đối số) {
Bản demo SemaphoreDemo = mới SemaphoreDemo();
vì (int tôi = 0; 6; i++) {
Sợi xe = mới Chủ đề(mới Ô tô(demo.parkingSpaces), "Xe hơi-" + (tôi + 1));
car.start();
}
}
}
Trong ví dụ này, đối tượng Semaphore parkSpaces được khởi tạo là 3, cho biết có 3 chỗ đỗ xe có sẵn. Mỗi ô tô (luồng) phải gọi phương thức Acacqui() trước khi cố gắng lấy chỗ đỗ xe. Sau khi lấy được nó thành công, hành động đỗ xe có thể được thực hiện sau khi hoàn thành việc đỗ xe, chỗ đỗ xe sẽ được giải phóng bằng cách gọi phương thức Release(). để các phương tiện khác có thể tiếp tục đỗ. Bằng cách này, việc lập kế hoạch và đồng bộ hóa tài nguyên đa luồng dựa trên semaphore được thực hiện.
「Tóm tắt"
Trong lập trình đa luồng Java, giao tiếp giữa các luồng là liên kết chính để đạt được công việc cộng tác và các hoạt động được đồng bộ hóa. Bài viết này khám phá nhiều phương pháp giao tiếp liên luồng hiệu quả thông qua một loạt ví dụ và giải thích chi tiết.
-
「Khóa và đồng bộ hóa" Cơ chế khóa là phương pháp đồng bộ hóa cơ bản nhất trong Sử dụng Java.
đồng bộ
Lớp Khóa từ khóa hoặc rõ ràng đảm bảo rằng chỉ có một luồng truy cập tài nguyên được chia sẻ cùng một lúc. Ví dụ mã cho thấy cách sử dụng khóa đối tượng để đảm bảo rằng luồng B bắt đầu thực thi sau khi luồng A hoàn thành việc thực thi, nhờ đó đạt được sự thực thi có trật tự giữa các luồng.
-
「Cơ chế chờ/thông báo"
Chờ đợi()
Và
thông báo()
Các phương thức cung cấp một cách giao tiếp luồng linh hoạt, cho phép các luồng chuyển sang trạng thái chờ khi các điều kiện cụ thể được đáp ứng và được các luồng khác đánh thức khi điều kiện thay đổi. Một ví dụ minh họa cách luồng A và luồng B in các số lần lượt, thể hiện ứng dụng của cơ chế chờ/thông báo trong cộng tác luồng.
-
「đường ống thông tin liên lạc" PipedInputStream và PipedOutputStream (hoặc PipedReader và PipedWriter) do Java cung cấp thực hiện truyền luồng dữ liệu giữa các luồng và đặc biệt phù hợp với các tình huống trao đổi thông tin đơn giản. Trong trường hợp, hai luồng hoàn thành các thao tác đọc và ghi của luồng ký tự thông qua đường ống, cho thấy hiệu quả trực quan của giao tiếp đường ống.
-
「phương pháp tham gia"
Chủ đề.join ()
Phương thức này cho phép một luồng đợi luồng khác kết thúc trước khi tiếp tục thực hiện, đảm bảo thứ tự thực hiện giữa các luồng. Mã mẫu hiển thị luồng chính đang chờ luồng con hoàn thành phép tính trước khi tiếp tục.
-
「phương pháp ngủ"
Thread.sleep()
Làm cho luồng hiện tại tạm dừng trong một thời gian xác định, nhưng nó không giải phóng khóa. Nó chủ yếu được sử dụng để trì hoãn việc thực thi luồng. Mặc dù không có ví dụ cụ thể nào được đưa ra trong cuộc thảo luận này, nhưng vai trò của nó là kiểm soát tốc độ thực thi luồng.
-
「Lớp ThreadLocal" ThreadLocal cung cấp hàm biến cục bộ của luồng. Mỗi luồng có một bản sao độc lập, giải quyết vấn đề cách ly các biến được chia sẻ giữa các luồng. Ví dụ cho thấy ngay cả khi nhiều luồng tham chiếu đến cùng một cá thể ThreadLocal thì dữ liệu chúng lưu trữ và thu được không ảnh hưởng lẫn nhau.
-
「cơ chế tín hiệu" Từ khóa dễ bay hơi đảm bảo khả năng hiển thị của các biến giữa các luồng khác nhau và lớp Semaphore là một công cụ semaphore nâng cao hơn được sử dụng để quản lý số lượng luồng truy cập tài nguyên đồng thời. Mã mẫu mô phỏng kịch bản quản lý chỗ đỗ xe và triển khai kiểm soát quyền truy cập vào các nguồn lực hạn chế thông qua Semaphore.
Trong tương lai, với sự phát triển của công nghệ Java và sự phổ biến của CPU đa lõi, tầm quan trọng của giao tiếp đa luồng sẽ ngày càng trở nên nổi bật. Việc hiểu và nắm vững các phương thức giao tiếp liên luồng khác nhau được giới thiệu ở trên sẽ giúp các nhà phát triển thiết kế các chương trình đồng thời hiệu quả, ổn định và dễ bảo trì hơn. Đồng thời, các chương tiếp theo sẽ tiếp tục giải thích sâu về ngữ nghĩa bộ nhớ của từ khóa dễ bay hơi, việc sử dụng các ngữ nghĩa trong các tình huống phức tạp và nhiều công cụ giao tiếp luồng dựa trên JDK như CountDownLatch, CyclicBarrier, v.v., để làm phong phú thêm và hoàn thiện hệ thống kiến thức lập trình đa luồng.
Bài viết này sử dụng markdown.com.cn để sắp chữ.
Cuối cùng, bài viết này về đa luồng Java theo thuật ngữ đơn giản (5): Giao tiếp giữa các luồng kết thúc tại đây. Nếu bạn muốn biết thêm về đa luồng Java theo thuật ngữ đơn giản (5): Giao tiếp giữa các luồng, vui lòng tìm kiếm các bài viết CFSDN hoặc. tiếp tục duyệt các bài viết liên quan, tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!