Java Bài 41: Thread Tập 1 – Thread Và Các Khái Niệm

Posted by

Chào mừng các bạn đã đến với bài học Java số 41, bài học về Thread. Đây là bài học trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.

Thời gian qua có rất nhiều bạn hỏi mình kiến thức Java bao giờ sẽ kết thúc. Vâng, mình cũng muốn thông báo với các bạn rằng chúng ta cũng đang cận kề với các bài học cuối cùng lắm rồi. Tuy nhiên mình cũng muốn các bạn biết rằng, thực ra cái sự kết thúc này chỉ là ở chừng mực các bài học của mình mà thôi. Kiến thức Java còn rất nhiều, mình hi vọng sẽ còn được nói nhiều hơn về các khía cạnh khác của Java ở các chủ đề khác. Và trong khi các bài viết sẽ không thể theo kịp tốc độ ham học hỏi của các bạn, thì mình mong các bạn cũng nên “thủ sẵn” cho riêng bạn các kho tài liệu học tập Java giá trị khác nhé.

Quay lại bài học về Thread. Như mình có nói từ bài học số 1, ngay khi bạn vừa mới chập chững biết đến Java là gì, thì mình cũng có nói đến một trong những thế mạnh của ngôn ngữ này, đó là Hỗ trợ chạy đa nhiệm (Multithread) rất tốt. Và mãi cho đến bài học hôm nay, thì chúng ta mới cùng nhau làm rõ hơn cái sự mạnh mẽ này của Java.

Khái Niệm Thread, Hay Multithread

Thread hay Multithread đều có ý nghĩa như nhau trong kiến thức của bài học này. Thread dịch ra tiếng Việt là Luồng, và MultithreadĐa luồng. Luồng ở đây chính là Luồng xử lý của hệ thống. Và bởi vì lý do chính đáng để cho Thread ra đời cũng chính là để cho các ứng dụng có thể điều khiển nhiều Thread khác nhau một cách đồng thời, mà nhiều Thread đồng thời như vậy cũng có nghĩa là Đa Thread, hay Multithread. Chính vì vậy mà kiến thức Thread hay Multithread cũng chỉ là một.

Vai trò của Thread hay Multithread dĩ nhiên là cái gì đó liên quan đến Đa Luồng, Đa Nhiệm rồi. Nói cụ thể ra đó là hệ thống sẽ hỗ trợ chúng ta tách các tác vụ của ứng dụng ra làm nhiều Luồng (hay Thread), và hệ thống sẽ giúp xử lý các Luồng này một cách đồng thời. Như vậy nếu theo như những gì chúng ta làm quen với Java từ trước đến giờ, đó là nếu chúng ta có các tác vụ A, B, C, với các cách code cũ, hệ thống sẽ luôn xử lý tuần tự các tác vụ này, giả sử A sẽ được xử lý trước tiên, rồi đến B, và cuối cùng là đến C. Nhưng sau bài học hôm nay, nếu chúng ta tổ chức sao cho mỗi A, BC là mỗi Thread, thì sẽ rất tuyệt vì chúng ta hoàn toàn có thể kêu hệ thống xử lý cả A, B và C cùng một lúc.

Một ví dụ thực tế để dễ hình dung hơn về Thread như sau. Giả sử bạn đang dùng ứng dụng Google Maps trên nền Android được viết bằng Java. Bạn có nhu cầu tìm một địa chỉ trên bản đồ, địa chỉ đó là “Chợ Bến Thành” chẳng hạn.

Mo phỏng yêu cầu Thread cho Google Maps

Khi bạn gõ từng chữ cái vào khung tìm kiếm ở trên cùng của màn hình, bạn mong muốn được thấy các gợi ý địa điểm sẽ liên tục được cập nhật theo từng chữ bạn gõ vào ở dưới. Như hình trên. Bạn thấy rằng, nếu không có Thread, hệ thống sẽ đợi bạn gõ xong một chữ, rồi mới bắt đầu tìm kiếm và gợi ý các địa điểm liên quan đến chữ đó, và trong lúc hệ thống đang tìm kiếm, bạn không thể gõ được chữ kế tiếp, vì với cách làm này, cùng một lúc hệ thống chỉ đáp ứng một chuyện thôi, hoặc là nhận ký tự bạn gõ hoặc là tìm kiếm, hết. Nhưng với việc áp dụng Multithread, chúng ta sẽ có 2 Thread chạy song song, một Thread nhận dữ liệu nhập vào của người dùng, Thread còn lại cứ dựa vào dữ liệu đã nhập đấy mà tìm kiếm, điều này làm cho trải nghiệm của người dùng được trọn vẹn, hệ thống mượt mà, nhanh chóng, không bị giật.

Bạn có thể xem mình minh họa việc đáp ứng của hệ thống đối với từng trường hợp tìm kiếm như ví dụ trên ở hình bên dưới. Trong đó, để tìm thấy kết quả “Chợ Bến Thành” xuất hiện trong danh sách gợi ý. Thì với cột Không Multithread bên trái, việc bạn gõ một từ rồi đợi hệ thống tìm kiếm, thì kết quả tìm được sẽ rất lâu so với cột MultiThread bên phải, việc bạn gõ và việc hệ thống tìm kiếm được tách ra, và thực hiện một cách song song nhau, là rất nhanh chóng và dễ chịu.

Mô phỏng việc tìm kiếm địa chỉ với Thread

Phân Biệt Các Khái Niệm Liên Quan

Mục này mình nói thêm, cho các bạn mới tiếp cận với Thread (thậm chí với các bạn đã hiểu rõ Thread rồi) đỡ bị lăn tăn khi đọc các tài liệu liên quan đến kiến thức hôm nay. Khi đó hẳn các bạn sẽ bị loạn lên với các khái niệm sau: Thread, Multithread, Task, Multitask, Process, Multiprocess.

Đầu tiên, ở mức bao quát nhất, bạn đều biết các hệ thống máy tính ở thời đại ngày nay đều được nhắc đến với việc có khả năng xử lý đa nhiệm. Đa nhiệm ở đây chính là việc xử lý nhiều nhiệm vụ cùng một lúc. Việc xử lý đa nhiệm này càng ngày càng phổ biến và được quan tâm nhiều hơn khi các dòng máy tính đều cho ra đời các cỗ máy với nhiều CPU. Như vậy cái nhiệm vụ mà hệ thống cần xử lý đó thường được gọi là Task, và một hệ thống đa nhiệm được gọi là Multitask.

Từ nhu cầu muốn xử lý Multitask đó, hệ thống mới định ngĩa ra các Process. Process được hiểu là một chương trình. Khi ứng dụng của bạn được khởi chạy, chúng sẽ được hệ thống tạo ra một Process và ứng dụng đó sẽ được thực thi và được quản lý bên trong Process đó. Hệ thống sẽ quản lý một lúc nhiều Process khác nhau, mỗi Process như vậy được cấp phát các tài nguyên hệ thống một cách độc lập với nhau. Và bởi cùng một lúc có thể có nhiều Process (hay nhiều ứng dụng) chạy song song với nhau, nên người ta gọi cái sự Đa Process này là Multiprocess.

Khi một Process được tạo, ứng dụng bên trong Process đó có thể tạo ra nhiều Thread khác. Về cơ bản thì Thread cũng sẽ được hệ thống quản lý như Process vậy, tức là chúng có thể được chạy song song với nhau, nên mới có thêm khái niệm Multithread. Nhưng các Thread bên trong một Process chỉ được hoạt động bên trong giới hạn của Process đó thôi. Các Thread sẽ được sử dụng các tài nguyên y như là Process của nó được phép. Nhưng có một khác biệt đó là các Thread rất nhẹ và có thể dễ dàng chia sẻ tài nguyên dùng chung với nhau bên trong một Process.

Như vậy thôi. Và mặc dù lan man nhiều khái niệm như vậy, nhưng bạn nên nhớ, bài học này chúng ta chỉ quan tâm đến một loại khái niệm mà thôi, đó chính là Thread.

Khi Nào Thì Sử Dụng Thread

Câu hỏi này tuy dễ trả lời, song nếu suy nghĩ kỹ nó lại chứa đựng nhiều thông tin hơn chúng ta tưởng.

Đầu tiên như mình có nói đến một cách giông dài trên kia rằng, nếu như bạn muốn có nhiều tác vụ muốn làm việc đồng thời với nhau, thì hãy sử dụng các Thread. Chẳng hạn bạn vừa muốn ứng dụng lấy thông tin người dùng nhập gì vào khung hội thoại, vừa gọi lên server để kiểm tra kết quả nhập vào. Hay nếu bạn lập trình game, bạn vừa muốn game nhận tín hiệu điều khiển từ bàn phím, song song với việc vẽ nhân vật trên màn hình theo sự điều khiển đó, song song với việc điều khiển các chướng ngại vật, hoặc điều khiển các đường đạn bắn, các hiển thị về điểm số, v.v…

Ý thứ hai trong việc sử dụng Thread là, cũng na ná như việc muốn xử lý các tác vụ đồng thời, nhưng khi này bạn mong muốn được điều khiển các Luồng theo một sự đồng bộ nhất định. Chẳng hạn bạn khởi động nhiều Thread trong một ứng dụng, nhưng bạn muốn các Thread khác tuy được khởi động nhưng khoan hãy chạy, mà phải đợi một Thread nào đó kết thúc thì mới được hoạt động. Ở Tập 3 của loạt bài về Thread mình sẽ nói rõ cách sử dụng này của Thread.

Ý thứ ba trong việc sử dụng Thread, cũng không ngoài việc muốn xử lý các tác vụ đồng thời, nhưng khi này là tình huống khi bạn muốn ứng dụng thực thi các tác vụ quá lớn. Lớn ở đây thường là lớn về mặt thời gian. Ví dụ khi ứng dụng phải download một file từ server, hay phải thực hiện công việc gì đó mà thời gian hoàn thành của nó không phải tức thời. Khi đó nếu không có Thread, các tác vụ lâu lắc này có thể sẽ làm ảnh hưởng nghiêm trọng đến trải nghiệm của người dùng.

Chúng ta sẽ đến phần ví dụ về Thread thông qua bài thực hành sau đây để giúp bạn hiểu rõ hơn.

Thực Hành Xây Dựng Ứng Dụng Quay Số Ngẫu Nhiên

Bài thực hành này giúp bạn có một cảm quan ban đầu về việc Thread là gì và nó hoạt động như thế nào. Mình không mong muốn các bạn phải hiểu hết các dòng code của bài thực hành này, nhưng mình hi vọng các bạn sẽ thử code, và thực thi thành công chương trình. Nếu có bất kỳ lỗi nào phát sinh xảy ra với bạn thì cứ liên lạc với mình hoặc để lại comment ở dưới bài viết hôm nay nhé.

Bạn cứ tưởng tượng rằng bài thực hành này đang xây dựng một trò chơi. Trong trò chơi này có một bàn xoay, trên đó có đánh các con số từ 0 đến 100. Người chơi sẽ bắt đầu xoay bàn xoay, sau khi bàn xoay được xoay thì người chơi chẳng nhìn thấy các con số trên đó, cho đến khi họ dừng bàn xoay lại, thì con số nào ở ngay người chơi chính là con số được người chơi chọn lựa.

Ồ, ứng dụng của chúng ta sẽ không xây dựng theo kiểu một trò chơi hoàn chỉnh như thế đâu. Thay vào đó chúng ta sẽ tập trung vào logic của trò chơi. Chúng ta sẽ thay việc xoay bàn xoay bằng một lệnh gõ vào ký tự bất kỳ trên console. Sau khi nhận ký tự vừa gõ, thay vì có một bàn xoay xoay tít, thì chúng ta sẽ khởi chạy một Thread, Thread này sẽ lần lượt “quay” các con số, sao cho nó đếm tuần tự từ 0 đến 100 rồi lại quay lại số 0. Thread của chúng ta chạy mãi cho đến khi người chơi nhập thêm một ký tự từ console và Thread kết thúc. Con số mà Thread vừa “dừng” lại chính là số được người chơi chọn lựa.

Bạn đã hiểu yêu cầu của việc xây dựng trò chơi này rồi đúng không nào. Để bắt đầu vào xây dựng trò chơi, bạn nên tự tạo một project mới. Bạn có thể đặt tên cho project này là ThreadLearning cũng được. Sau đó tạo mới một class có chứa phương thức main() trong đó.

Tiếp theo, bạn hãy tự tạo mới một lớp mới có tên CountTheNumberThread. Khoan hãy code gì cho lớp này cả. Lớp này chính là Thread với trách nhiệm “quay vòng” các con số như chúng ta đã bàn đến. Tổng quan thì ứng dụng của chúng ta có cấu trúc như hình sau.

Cấu trúc project thực hành Thread

Nào giờ bạn hãy code cho thân lớp CountTheNumberThread như sau.

public class CountTheNumberThread extends Thread {
	
	private int count = 0;
	private boolean isStop = false;

	@Override
	public void run() {
		while(!isStop) {
			count++;
			if (count > 100) {
				count = 0;
			}
		}
	}
	
	public void end() {
		isStop = true;
	}
	
	public int getCount() {
		return count;
	}
}

Dù cho có thể bạn chưa hiểu rõ về Thread, nhưng mình cũng muốn giải thích một chút, để đến bài học sau bạn sẽ dễ dàng tiếp cận với Thread hơn. Đầu tiên thì lớp CountTheNumberThread muốn thành một Thread thì nó phải kế thừa từ lớp Thread (và còn có cách khác nữa để biến một lớp thành Thread mà bài sau chúng ta sẽ nói đến). Sau đó bên trong lớp này phải override phương thức run(). Chính các code bên trong phương thức run() sẽ là một Luồng, Luồng này sẽ được hệ thống thực thi song song với các Luồng đang thực thi khác nếu có trong hệ thống. Vậy thôi, ngoài hai ý lớn mình nói đến như này ra thì các khai báo còn lại của CountTheNumberThread đều quá đỗi quen thuộc nên mình không trình bày nhiêu khê.

Đến lúc này thì CountTheNumberThread cũng chỉ là một lớp mà thôi. Muốn khởi chạy lớp này để trở thành một Thread thì mình mời bạn đến với các code ở phương thức main() như sau.

public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in);
		
	// Đợi người dùng nhấn một phím để bắt đầu
	System.out.println("Nhấn phím bất kỳ để quay số");
	scanner.nextLine();
		
	// Khai báo & Khởi chạy CountTheNumberThread như là một Thread thông qua
	// phương thức start()
	CountTheNumberThread countingThread = new CountTheNumberThread();
	countingThread.start();
		
	// Đợi người dùng nhấn một phím để kết thúc
	System.out.println("Nhấn phím bất kỳ để kết thúc quay số");
	scanner.nextLine();
		
	// Ngừng Thread và xem hiện đang "quay" tới số nào
	countingThread.end();
	System.out.println("Con số may mắn: " + countingThread.getCount());
}

Nhìn vào code trên, bạn có thể thấy chút khác biệt giữa việc sử dụng một Thread so với một lớp bình thường khác. Nếu như việc khởi tạo một lớp bình thường mà bạn biết so với một Thread là như nhau. Thì với một Thread, để khởi chạy Thread đó (tức là bạn biến nó thành một Luồng trong hệ thống), bạn phải gọi phương thức start() của nó, đây là phương thức được xây dựng sẵn ở lớp Thread. Khi start() được gọi, Thread cha sẽ khởi chạy các code bên trong phương thức run() mà bạn đã khai báo bên trong CountTheNumberThread. Và thế là việc đón nhận dữ liệu gõ vào từ bàn phím ở phương thức main() sẽ song song với vòng lặp bên trong phương thức run() này, không code nào phải chờ đợi code nào cả.

Nếu bạn thực thi chương trình, hãy nhập một ký tự vào console để bắt đầu “quay số” và nhập một lần nữa để kết thúc, bạn sẽ nhận được một số may mắn của riêng bạn như sau.

Kết quả console bài thực hành Thread

Opps! Mình quay ra số 69, còn bạn thì sao. Chúc bạn quay tay may mắn. 😉

Bài Tập

Với bài tập này mình muốn bạn hãy thử bỏ qua việc kế thừa Thread của lớp CountTheNumberThread, khi đó thì phương thức run() của lớp này cũng không còn từ khóa override nữa, như vậy bạn đã biến lớp này thành một lớp bình thường và run() cũng chỉ là một phương thức bình thường khác. Rồi sau đó ở main() bạn đừng gọi countingThread.start() nữa mà hãy gọi countingThread.run(). Tức là bạn không sử dụng Thread trong trường hợp này. Bạn hãy thực thi lại ứng dụng và so sánh xem việc áp dụng Thread với trò chơi này sẽ khác với việc không áp dụng Thread sẽ như thế nào nhé.

Chúng ta vừa mới tiếp cận kiến thức khá mới mẻ và thú vị của Java về vấn đề Đa nhiệm, cụ thể là Thread. Bài hôm nay chỉ mới là các kiến thức làm quen ban đầu, Thread còn rất nhiều kiến thức thú vị khác mà mình sẽ lần lượt trình bày ở các bài viết sắp tới nữa.

Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:

Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
Comment bên dưới mỗi bài nếu có thắc mắc.
Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.

Bài Kế Tiếp

Sau bài làm quen hôm nay, chúng ta sẽ đến phần chính của Thread. Đó chính là các kiến thức về các cách khởi tạo một Thread. Và vì Thread được xem như một tác vụ độc lập, nên chúng ta cũng sẽ xem việc kết nối với nhau giữa các Thread như thế nào nhé.

 

Advertisements
Rating: 5.0/5. From 4 votes.
Please wait...

5 comments

  1. Các anhh cho em hỏi, lệnh start(). Chỉ gọi 1 lần thoi đúng không ạk. Em dùng while(true) thì bị lỗi

    1. Có thể do while(true) của bạn làm Thread chạy hoài không ngừng gây ra lỗi về memory đấy. Bạn đừng để vòng lặp chạy lâu quá, nên dừng sớm sớm.

  2. Ra bài nhanh lên bạn ơi, mấy tuần mới ra 1 bài mà đọc 5 phút là xong :((. Bài bạn giảng rất dễ hiểu ạ !

      1. Haha dù sao cũng cảm ơn hai bạn đã ủng hộ mình, dù bằng cách này hay cách khác. Mình vẫn muốn viết nhiều hơn bây giờ, mỗi lúc rảnh là mình lại viết thôi. Phần 2 của bài Thread sắp xong rồi nhé.

Gửi phản hồi