CodeGym/Blog Java/Ngẫu nhiên/Cùng nhau tốt hơn: Java và lớp Thread. Phần I - Chủ đề th...
John Squirrels
Mức độ
San Francisco

Cùng nhau tốt hơn: Java và lớp Thread. Phần I - Chủ đề thực hiện

Xuất bản trong nhóm

Giới thiệu

Đa luồng đã được tích hợp vào Java ngay từ đầu. Vì vậy, chúng ta hãy xem xét ngắn gọn về thứ được gọi là đa luồng. Cùng nhau tốt hơn: Java và lớp Thread.  Phần I — Chủ đề thực thi - 1Chúng tôi lấy bài học chính thức từ Oracle làm điểm tham khảo: " Bài học: Ứng dụng "Xin chào thế giới!" ". Chúng tôi sẽ thay đổi một chút mã của chương trình Hello World như sau:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argslà một mảng các tham số đầu vào được truyền khi chương trình được bắt đầu. Lưu mã này vào một tệp có tên khớp với tên lớp và có phần mở rộng .java. Biên dịch nó bằng tiện ích javac : javac HelloWorldApp.java. Sau đó, chúng tôi chạy mã của mình với một số tham số, ví dụ: "Roger": java HelloWorldApp Roger Cùng nhau tốt hơn: Java và lớp Thread.  Phần I — Chủ đề thực hiện - 2Mã của chúng tôi hiện có một lỗ hổng nghiêm trọng. Nếu bạn không chuyển bất kỳ đối số nào (nghĩa là chỉ thực thi "java HelloWorldApp"), thì chúng tôi sẽ gặp lỗi:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Đã xảy ra ngoại lệ (nghĩa là lỗi) trong chuỗi có tên "chính". Vì vậy, Java có chủ đề? Đây là nơi cuộc hành trình của chúng tôi bắt đầu.

Java và chủ đề

Để hiểu luồng là gì, bạn cần hiểu chương trình Java bắt đầu như thế nào. Hãy thay đổi mã của chúng tôi như sau:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			// Do nothing
		}
	}
}
Bây giờ hãy biên dịch lại với javac. Để thuận tiện, chúng ta sẽ chạy mã Java trong một cửa sổ riêng. Trên Windows, điều này có thể được thực hiện như sau: start java HelloWorldApp. Bây giờ chúng ta sẽ sử dụng tiện ích jps để xem những thông tin mà Java có thể cho chúng ta biết: Cùng nhau tốt hơn: Java và lớp Thread.  Phần I — Chủ đề thực thi - 3Số đầu tiên là PID hoặc ID tiến trình. một quá trình là gì?
A process is a combination of code and data sharing a common virtual address space.
Với các quy trình, các chương trình khác nhau được tách biệt với nhau khi chúng chạy: mỗi ứng dụng sử dụng vùng riêng trong bộ nhớ mà không can thiệp vào các chương trình khác. Để tìm hiểu thêm, tôi khuyên bạn nên đọc hướng dẫn này: Quy trình và Chủ đề . Một quá trình không thể tồn tại mà không có một luồng, vì vậy nếu một quá trình tồn tại, thì nó có ít nhất một luồng. Nhưng điều này xảy ra như thế nào trong Java? Khi chúng ta bắt đầu một chương trình Java, việc thực thi bắt đầu bằng mainphương thức. Như thể chúng ta đang bước vào chương trình, vì vậy mainphương pháp đặc biệt này được gọi là điểm vào. Phương mainthức phải luôn là "public static void", để máy ảo Java (JVM) có thể bắt đầu thực thi chương trình của chúng ta. Để biết thêm thông tin, Tại sao phương thức chính của Java lại tĩnh?. Hóa ra trình khởi chạy Java (java.exe hoặc javaw.exe) là một ứng dụng C đơn giản: nó tải các tệp DLL khác nhau thực sự bao gồm JVM. Trình khởi chạy Java thực hiện một nhóm lệnh gọi Giao diện gốc Java (JNI) cụ thể. JNI là một cơ chế để kết nối thế giới của máy ảo Java với thế giới của C++. Vì vậy, trình khởi chạy không phải là bản thân JVM, mà là một cơ chế để tải nó. Nó biết các lệnh chính xác cần thực thi để khởi động JVM. Nó biết cách sử dụng lệnh gọi JNI để thiết lập môi trường cần thiết. Tất nhiên, việc thiết lập môi trường này bao gồm việc tạo luồng chính, được gọi là "chính". Để minh họa rõ hơn luồng nào tồn tại trong quy trình Java, chúng tôi sử dụng lệnh jvisualvmcông cụ, được bao gồm trong JDK. Biết được pid của một tiến trình, chúng ta có thể thấy ngay thông tin về tiến trình đó: jvisualvm --openpid <process id> Cùng nhau tốt hơn: Java và lớp Thread.  Phần I — Chủ đề thực thi - 4Điều thú vị là mỗi luồng có một vùng riêng biệt trong bộ nhớ được cấp phát cho tiến trình. Cấu trúc bộ nhớ này được gọi là ngăn xếp. Một ngăn xếp bao gồm các khung. Một khung đại diện cho việc kích hoạt một phương thức (một cuộc gọi phương thức chưa hoàn thành). Một khung cũng có thể được biểu diễn dưới dạng StackTraceElement (xem API Java cho StackTraceElement ). Bạn có thể tìm thêm thông tin về bộ nhớ được phân bổ cho mỗi luồng trong cuộc thảo luận tại đây: " Java (JVM) phân bổ ngăn xếp cho mỗi luồng như thế nào ". Nếu bạn nhìn vào API Java và tìm kiếm từ "Thread", bạn sẽ tìm thấy java.lang.Threadlớp học. Đây là lớp đại diện cho một luồng trong Java và chúng ta sẽ cần làm việc với nó. Cùng nhau tốt hơn: Java và lớp Thread.  Phần I — Chủ đề thực thi - 5

java.lang.Thread

Trong Java, một luồng được biểu diễn bằng một thể hiện của java.lang.Threadlớp. Bạn nên hiểu ngay rằng bản thân các thể hiện của lớp Thread không phải là các luồng thực thi. Đây chỉ là một loại API dành cho các luồng cấp thấp được quản lý bởi JVM và hệ điều hành. Khi chúng tôi khởi động JVM bằng trình khởi chạy Java, nó sẽ tạo một mainluồng có tên là "chính" và một số luồng quản lý khác. Như đã nêu trong JavaDoc cho lớp Chủ đề: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Có 2 loại thread: daemon và non-daemon. Chủ đề daemon là chủ đề nền (dọn phòng) thực hiện một số công việc trong nền. Từ "daemon" dùng để chỉ con quỷ của Maxwell. Bạn có thể tìm hiểu thêm trong bài viết Wikipedia này . Như đã nêu trong tài liệu, JVM tiếp tục thực thi chương trình (quy trình) cho đến khi:
  • Phương thức Runtime.exit() được gọi là
  • Tất cả các luồng KHÔNG phải daemon đều hoàn thành công việc của chúng (không có lỗi hoặc có ngoại lệ bị ném)
Một chi tiết quan trọng sau đây: chủ đề daemon có thể bị chấm dứt tại bất kỳ thời điểm nào. Do đó, không có gì đảm bảo về tính toàn vẹn của dữ liệu của họ. Theo đó, các luồng daemon phù hợp với một số nhiệm vụ vệ sinh nhất định. Ví dụ, Java có một luồng chịu trách nhiệm xử lý finalize()các lệnh gọi phương thức, tức là các luồng liên quan đến Trình thu gom rác (gc). Mỗi luồng là một phần của một nhóm ( ThreadGroup ). Và các nhóm có thể là một phần của các nhóm khác, tạo thành một hệ thống phân cấp hoặc cấu trúc nhất định.
public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Các nhóm mang lại trật tự cho quản lý luồng. Ngoài các nhóm, các luồng có trình xử lý ngoại lệ riêng. Hãy xem một ví dụ:
public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
Phép chia cho 0 sẽ gây ra lỗi mà trình xử lý sẽ bắt được. Nếu bạn không chỉ định trình xử lý của riêng mình, thì JVM sẽ gọi trình xử lý mặc định, trình xử lý này sẽ xuất dấu vết ngăn xếp của ngoại lệ thành StdError. Mỗi chủ đề cũng có một ưu tiên. Bạn có thể đọc thêm về mức độ ưu tiên trong bài viết này: Mức độ ưu tiên luồng Java trong đa luồng .

Tạo chủ đề

Như đã nêu trong tài liệu, chúng tôi có 2 cách để tạo chủ đề. Cách đầu tiên là tạo lớp con của riêng bạn. Ví dụ:
public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
Như bạn có thể thấy, công việc của tác vụ xảy ra trong run()phương thức, nhưng bản thân luồng được bắt đầu trong start()phương thức. Đừng nhầm lẫn các phương thức này: nếu chúng ta gọi un()trực tiếp phương thức r, thì sẽ không có luồng mới nào được bắt đầu. Đây là start()phương thức yêu cầu JVM tạo một luồng mới. Tùy chọn này nơi chúng tôi kế thừa Chủ đề đã tệ ở chỗ chúng tôi đưa Chủ đề vào hệ thống phân cấp lớp của chúng tôi. Hạn chế thứ hai là chúng ta đang bắt đầu vi phạm nguyên tắc "trách nhiệm duy nhất". Nghĩa là, lớp của chúng ta đồng thời chịu trách nhiệm điều khiển luồng và một số tác vụ được thực hiện trong luồng này. Đâu là cách đúng đắn? Câu trả lời được tìm thấy trong cùng một run()phương thức mà chúng tôi ghi đè:
public void run() {
	if (target != null) {
		target.run();
	}
}
Đây targetlà một số java.lang.Runnable, mà chúng ta có thể chuyển qua khi tạo một thể hiện của lớp Thread. Điều này có nghĩa là chúng ta có thể làm điều này:
public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnablecũng là một giao diện chức năng kể từ Java 1.8. Điều này giúp bạn có thể viết mã đẹp hơn nữa cho tác vụ của luồng:
public static void main(String[] args) {
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Phần kết luận

Tôi hy vọng cuộc thảo luận này sẽ làm rõ luồng là gì, luồng ra đời như thế nào và những thao tác cơ bản nào có thể được thực hiện với luồng. Trong phần tiếp theo , chúng ta sẽ cố gắng hiểu cách các luồng tương tác với nhau và khám phá vòng đời của luồng. Kết hợp 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 Together tốt hơn: Java và lớp Thread. Phần VI — Bắn đi!
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào