Giải pháp chuyển đổi số trọn gói cho doanh nghiệp Việt
Như vậy là sau tập đầu tiên của Thread Pool, bạn đã biết được rằng mình đang cố gắng giúp bạn ghi nhớ được ý nghĩa của kỹ thuật này như là một cái Hồ điều tiết Thread. Từ việc hiểu ý nghĩa đó, bạn cũng đã nắm được công năng và nắm được có bao nhiêu cách để có thể tổ chức nên cái hồ này.
Tuy nhiên tất cả cũng vẫn dừng lại ở lý thuyết. Việc hiểu tường tận hơn từng cách sử dụng Hồ, và làm quen với các ví dụ, chính là mục đích chính bắt đầu từ bài học hôm nay.
Mặc dù mình đã cố gắng trình bày hết mức có thể cách sử dụng cũng như ý nghĩa của các phương thức mà bộ ba các lớp của bài hôm nay mang lại, nhưng mình biết sẽ còn thiếu sót nhiều lắm. Một phần vì giới hạn của bài viết, tránh viết quá dài (thực ra nó cũng dài lắm rồi, và tốn rất nhiều thời gian để viết bài này), phần nữa vì mình cũng chưa có cơ hội sử dụng hết tất cả các kiến thức mà Thread Pool mang lại, vì chúng khá rộng lớn. Nên qua bài học nếu bạn nào có những đóng góp, xây dựng thì hãy liên lạc với mình qua các kênh mình liệt kê ở cuối bài viết hôm nay nhé.
Nào chúng ta cùng bắt đầu làm quen với cách sử dụng Thread Pool thông qua bộ ba tiện ích: Executors, Executor và ExecutorService.
Như đã giới thiệu từ bài học trước, bộ ba Executors, Executor và ExecutorService giúp bạn sử dụng ThreadPoolExecutor được dễ dàng hơn. Cụ thể chúng là gì thì mình có thể diễn đạt ra như sau.
Ví dụ sau cho thấy code của việc khai báo một Thread Pool có thể thực thi đồng thời 3 Thread một lúc.
Trước khi bắt đầu làm quen với các bài thực hành để biết rõ hơn về việc sử dụng Thread Pool. Thì mình mời các bạn cùng làm quen đến một số phương thức mà Helper Class Executors mang đến, để xem lớp này giúp bạn “triệu hồi” các thể loại Thread Pool nào nhé.
Như bạn biết, Executors là một Helper Class, nó chính là một lớp đắc lực để bạn có thể thi triển các ThreadPoolExecutor mà không cần biết tí gì về ThreadPoolExecutor cả. Chung quy lại thì Executors có các phương thức xây dựng sẵn hữu ích mà mình biết như sau (ngoài các phương thức này ra còn khá là nhiều các phương thức khác mà thú thiệt mình vẫn chưa có cơ hội tìm hiểu đến, nếu được bạn hãy tự mày mò tìm hiểu thêm nhé).
Chúng ta sẽ cùng làm quen với các thể loại Thread Pool được liệt kê trên đây mà lớp Executors cung cấp thông qua các bài thực hành sau. Các bạn cùng code với mình nhé.
Như đã nói trên kia, phương thức newSingleThreadExecutors của Executors giúp tạo ra một Thread Pool mà chỉ có duy nhất 1 Thread được thực thi một lần.
Để kiểm chứng, chúng ta cùng xây dựng Thread Pool này. Nhưng trước hết, mình muốn các bạn xây dựng một Runnable để chúng ta có thể truyền chúng vào trong Thread Pool mà chúng ta sẽ xây dựng ở bước kế tiếp sau. Runnable này mình đặt tên là MyRunnable nhé.
Trên đây là một Runnable bình thường thôi. Tại sao chúng ta lại phải sử dụng Runnable? Vì Thread Pool chỉ nhận các Runnable truyền vào (và Callable nữa mà bạn sẽ làm quen sau, nhưng Callable cũng giống Runnable mà thôi). Bạn nên nhớ là các Runnable khi chưa gọi phương thức start() thì vẫn chưa được hệ thống thực thi thành các Thread. Bạn có thể xem lại kiến thức này ở bài học về Thread này. Do đó việc chúng ta truyền các Runnable vào Thread Pool là để cho Thread Pool sắp xếp chúng vào hàng đợi. Đến khi quyết định thực thi Runnable nào, các Thread Pool sẽ tiến hành start() Runnable đó.
Tiếp theo sau là code ở phương thức main.
Bạn có thể thấy, dòng đầu tiên của code trên khai báo một Thread Pool thông qua phương thức Executors.newSingleThreadExecutor(). Phương thức này của Executors giúp tạo một Thread Pool với chỉ duy nhất một Thread được thực thi. Thread Pool này được quản lý thông qua biến executorService, đây chính là lớp ExecutorService mà chúng ta sẽ tìm hiểu sau. Bạn chỉ cần biết thêm là phương thức executorService.execute(myRunnable) giúp lần lượt đưa các Runnable được khởi tạo vào trong Thread Pool và lần lượt thực thi sau đó.
Đến đây bạn có thể thực thi chương trình để xem, bạn có thể thấy cứ mỗi 2 giây, chỉ có 1 Thread được chạy và in ra console mà thôi. Hình dưới là kết quả ở console của máy mình.
Thông qua bài thực hành trên, bạn đã hiểu cách làm việc với Thread Pool rồi. Bài thực hành số 2 này mình không nói nhiều. Bạn tự code lại theo khai báo mới của Thread Pool (như tô sáng ở code dưới) rồi trải nghiệm nhé. Lưu ý là chúng ta dùng lại MyRunnable và không thay đổi gì cả trong code của lớp này để cùng so sánh xem Thread Pool mới này sẽ cho ra kết quả khác như thế nào ở console.
Cũng tương tự, bạn hãy thử dùng newCachedThreadPool để tạo một Thread Pool xem. Với cách này hệ thống sẽ thực thi hết các Thread trong phương thức main của chúng ta.
Executor và ExecutorService là một. Tại sao thì mình đã có giải thích ở mục giới thiệu về chúng trên kia rồi. Do đặc tính này mà mình sẽ tập trung nói về ExecutorService, vì nó vừa bao gồm phương thức execute(Runnable) của Executor, vừa xây dựng thêm nhiều phương thức hữu ích khác nữa.
Bạn cũng có thể thấy rằng, thông qua các ví dụ trên đây, chúng ta đã cùng làm quen, và cùng hiểu sơ qua công dụng của ExecutorService rồi. Đó là nó giúp chúng ta đưa các Runnable vào bên trong Thread Pool thông qua phương thức execute(Runnable). Chính các Thread Pool sẽ quyết định thực thi các Runnable này theo kịch bản mà chúng được khai báo.
Sau đây là tất cả các phương thức mà ExecutorService cung cấp.
Chúng ta sẽ không thực hành với phương thức execute() nữa, vì các bài thực hành trước đã sử dụng rồi. Hãy cùng thực hành với các phương thức còn lại của ExecutorService nào.
Do bài thực hành này cũng dùng đến Runnable, nên mình sẽ vẫn dùng lại MyRunnable đã được xây dựng từ các bài thực hành trước. Bạn chú ý một chút cách gọi submit() để trả về danh sách các Future như sau.
Bạn thấy rằng kết quả sau đây có in ra các dòng “null”. Chúng là các kết quả của các lời gọi future.get(). Và vì phương thức submit() này của ExecutorService giúp trả về các kết quả theo kiểu đồng bộ, tức là khi Thread kết thúc thì null mới được trả về thông qua future.get(), nên dù cho bạn đã gọi chúng rất sớm, chúng vẫn sẽ chỉ in ra null khi nào Thread kết thúc mà thôi.
Bài thực hành này chúng ta cũng sẽ làm quen với Callable. Cơ bản thì Callable cũng khá giống với Runnable, nó cũng giúp để khởi tạo Thread trong ThreadPool. Nhưng Callable hữu ích hơn Runnable ở chỗ nó cho phép trả về một kết quả mà bạn định nghĩa sẵn. Chúng ta cùng đến với code khai báo một Callable như sau, rồi cùng nhau nói về nó tiếp theo nữa nhé. Callable này mình đặt tên là MyCallable.
Dòng đầu tiên bạn thấy có khai báo lớp MyCallable kế thừa từ Callable<String>. Cách viết Callable<String> mình sẽ nói rõ hơn khi nói về kiến thức Generic sau này. Cơ bản bạn có thể hiểu Callable<String> là một Callable với một ràng buộc trong code của bạn phải trả về kiểu dữ liệu là String. Nếu bạn muốn trả về kiểu dữ liệu khác thì cứ thay String bằng kiểu dữ liệu bạn muốn là được.
Tương tự, vì Callable<String> ràng buộc bạn phải trả về kiểu String, nên code bên trong nó bạn phải Override phương thức call() với kiểu trả về là String luôn. Phương thức call() này của Callable thay cho run() bên Runnable, chỉ khác là call() có đòi hỏi kết quả trả về. Lúc này chúng ta trả về biến name luôn và in dòng kết thúc ở bên ngoài nhé.
Với việc khai báo Callable như trên, mình sẽ tiến hành gọi như sau ở phương thức main. Bạn xem.
Có một điều chắc bạn cũng sẽ hơi nhức đầu. Đó là thay vì khai báo List<Future> như bài thực hành trước (bài học về List, ArrayList gì đó, chính là các Collection mình cũng sẽ nói sau), thì bây giờ lại phải khai báo List<Future<String>>. Bạn nên biết rằng, chắc chắn cái đoạn <String> ở khai báo List này có liên quan mật thiết với <String> ở khai báo lớp Callable trên kia rồi, và như đã nói, mình sẽ nói sau ở bài về Generic. Bây giờ khi thực thi chương trình, kết quả không nằm ngoài tiên liệu của chúng ta. Có điều do phương thức future.get() có một độ trễ nhất định, nên việc in ra Callable nào kết thúc không ngay tức thời, nên nhìn vào kết quả console sau, bạn sẽ thấy dường như có nhiều Thread được thêm vào Thread Pool hơn khai báo 5 Thread ban đầu.
Do bài học đã quá dài, nên mục invokeAny() mình chỉ cho các bạn xem code, và kết quả thực thi chương trình. Ý nghĩa của phương thức này mình đã giải thích trên kia rồi nhé. Có khác một tí là ví dụ này mình không dùng đến MyCallable nữa, mình muốn khai báo các Callable trực tiếp trong vòng for bằng cách dùng đến lớp vô danh cho code được gọn nhẹ.
Chưa có câu hỏi nào cho bài này.