Tìm hiểu về Thread Pool trong java

Các chuỗi được sinh ra để thực hiện các tác vụ cụ thể và nhiều chuỗi hoạt động cùng nhau để giúp chúng tôi giải quyết các vấn đề về thời gian và hiệu suất khi xử lý một tác vụ.

Câu hỏi đặt ra ở đây là, mỗi khi chúng tôi sinh ra nhiều chuỗi hơn, tác vụ của chúng tôi sẽ nhanh hơn và trơn tru hơn?

Bạn đang xem: Thread pool là gì

Câu trả lời là không phải lúc nào cũng tốt khi tạo nhiều luồng cùng lúc, bởi vì mỗi khi một luồng mới được tạo và bộ nhớ được cấp phát bằng một từ hóa mới, sẽ có vấn đề về bộ nhớ. và hiệu suất -> có thể khiến chương trình bị treo.

  • Để giải quyết vấn đề này, threadpool đã ra đời để giới hạn số lượng các luồng chạy đồng thời trong ứng dụng của chúng tôi.

ví dụ: Khi chúng ta viết chương trình tải tệp từ internet, mỗi tệp cần 1 luồng để thực hiện quá trình tải xuống, giả sử chúng ta cần tải xuống 10000 tệp âm thanh thì chúng ta cần tối đa 10000 các luồng hoạt động đồng thời trong cùng một chương trình. Điều này dễ dẫn đến quá tải chương trình, ảnh hưởng đến bộ nhớ và hiệu suất của chương trình, dễ dẫn đến treo máy vì khó kiểm soát.

do đó, để khắc phục hiện tượng này, java cho phép chúng ta không phải tạo một luồng mới cho mỗi tác vụ, tiến trình thực thi cùng một lúc, tác vụ và tiến trình có thể được đặt trong một nhóm luồng, để khi trong nhóm luồng bất kỳ luồng nào không. Khi một tác vụ được thực thi, một tác vụ sẽ được gán cho một trong các tiểu trình để thực thi. Điều này sẽ giúp khắc phục tắc nghẽn và chương trình sẽ kiểm soát luồng thực thi.

Trong nhóm chuỗi , các tác vụ sẽ được chèn vào hàng đợi chặn. Hàng đợi chặn có thể hiểu là nơi lưu trữ các tác vụ mà các luồng sẽ tuần tự truy xuất và thực thi. Mỗi khi một nhiệm vụ mới được thêm vào hàng đợi, thì chỉ một luồng không thực hiện tác vụ đó vào hàng đợi để lấy nhiệm vụ đó, các luồng còn lại phải đợi luồng đó lấy nó. Nhiệm vụ đã thành công.

Nhưng may mắn thay, bắt đầu với ** java 5 **, chúng tôi cung cấp thư viện “khung thực thi” trong gói java.util.concurrent giúp lập trình viên tạo và quản lý “nhóm luồng” và “luồng” nhà máy đơn giản hơn bao giờ. Java cung cấp cho chúng ta lớp người thi hành, giao diện con của nó là dịch vụ người thi hành và lớp threadpoolexecutor kế thừa từ giao diện dịch vụ người thi hành ở trên.

Tìm hiểu về Thread Pool trong java

Ở đây, chúng ta có thể thấy rằng đối tượng threadpool executive chấp nhận một runnable và đặt nó vào hàng đợi runnable . Hàng đợi này đại diện cho tất cả các nhiệm vụ được gửi đến nhóm luồng để thực thi. Bản thân threadpool là một chuỗi luồng đang đợi để kéo các hộp chạy từ hàng đợi và thực thi chúng trong phương thức run () của riêng chúng. Trong khi nhóm luồng đang chạy, hay nói cách khác, các luồng trong nhóm luồng vẫn còn sống và sẵn sàng thực thi có thể chạy được. Khi có một runnable mới trong hàng đợi, một trong các luồng sẽ kéo nó ra và gọi phương thức runnable của run ().

2.1. Ví dụ

Ví dụ sau sẽ trình bày cách tạo nhóm luồng bằng threadpoolexecutor:

run.java

testthreadpool.java

2.2. Cách thức hoạt động của trình thực thi nhóm luồng

Giải thích cách hoạt động của chương trình trên

Xem thêm: Tiểu sử ca sĩ DUY MẠNH ‘ Kiếp đỏ đen’ | Sự nghiệp và đời tư Duy Mạnh

Trong dòng mã khởi chạy trình xử lý luồng:

Chúng tôi có 6 tham số:

  • Tham số 1: (corepoolsize) là số luồng tối thiểu trong nhóm luồng. Khi khởi tạo, số luồng có thể là 0. Khi các tác vụ được thêm vào, các luồng mới được tạo ra, từ đây, nếu số luồng ít hơn corepoolsize, các luồng mới sẽ được tạo cho đến khi số luồng bằng giá trị của corepoolsize.
  • Tham số 2: (Maximumumpoolsize) là số luồng tối đa trong nhóm luồng.
  • Thông số 3: (keepalivetime): Khi số luồng lớn hơn corepoolsize, thì keepalivetime là luồng “nhàn rỗi” lâu nhất để chờ tác vụ. Khi hết thời gian chờ mà luồng không có nhiệm vụ, nó sẽ bị hủy.
  • Thông số 4: (unit) là đơn vị thời gian của keepalivetime. Trong ví dụ này, đơn vị của tôi là timeunit.seconds.
  • Tham số 5: (workqueue) là một hàng đợi để chứa các tác vụ mà các luồng sẽ nhận và thực thi từng cái một, ở đây tôi đang sử dụng arrayblockingqueue.
  • Đối số 6: (trình xử lý): Hành động khi yêu cầu (nhiệm vụ) bị từ chối

Tại đây, bạn có thể thấy mình đang sử dụng threadpoolexecutor.callerrunspolicy, là một trình xử lý lỗi tuyệt vời cho threadpoolexecutor, hãy cùng tìm hiểu: callerrunspolicy : Đây là trình xử lý lỗi, khi tác vụ được gọi khi bị từ chối vì lý do nào đó (có thể là do hàng đợi trong nhóm luồng đã đầy …), nó được chạy lại bởi một luồng mới khác (khi nhóm luồng có luồng không hoạt động), trừ khi trình thực thi bị tắt hoặc tác vụ bị hủy bỏ. Điều này có nghĩa là khi sử dụng callerrunspolicy, bạn sẽ không bao giờ lo lắng về việc chương trình của mình bỏ lỡ một tác vụ đã được gửi nhưng chưa được xử lý. Ngoài ra, chúng tôi cũng có thể sử dụng “chính sách” cho trình xử lý bị từ chối, như sau:

  • threadpoolexecutor.abortpolicy: Khi một tác vụ bị từ chối, chương trình ném một thời gian chạy từ chối thực hiện ngoại lệ.
  • threadpoolexecutor.discardpolicy: Khi một công việc bị từ chối, nó chỉ cần “hủy bỏ” (loại bỏ) mà không gây ra bất kỳ lỗi nào.
  • threadpoolexecutor.discardoldestpolicy: khi một công việc bị từ chối Khi một công việc bị bị từ chối, chương trình sẽ hủy nhiệm vụ “cũ nhất” không có gì nổi bật trong hàng đợi, sau đó gửi nhiệm vụ bị từ chối vào hàng đợi và cố gắng xử lý lại.

Kể từ java 5, nhóm luồng đã được tích hợp vào gói java.util.concurrent, vì vậy chúng tôi không cần tạo threadpool mà sử dụng các lớp có sẵn từ gói này. Java cung cấp cho chúng ta lớp thực thi và giao diện của lớp thực thi là dịch vụ thực thi . Giao diện thực thi đại diện cho một cơ chế thực thi không đồng bộ có khả năng thực thi các tác vụ trong nền. thi hành viên là một đối tượng chịu trách nhiệm quản lý các luồng và thực hiện các tác vụ có thể chạy được yêu cầu. Nó phân tách các chi tiết của việc tạo luồng, lập lịch, v.v., cho phép chúng tôi tập trung vào việc phát triển logic tác vụ mà không cần lo lắng về các chi tiết quản lý luồng.

3.1. Ví dụ

run.java

testthreadpool.java

3.2. Cách tạo một dịch vụ thực thi

Chúng tôi có thể tạo nhóm luồng thông qua dịch vụ thực thi và “nhiệm vụ” sẽ được gửi đến nhóm và xử lý bằng một trong các phương pháp được cung cấp bởi trình thực thi như sau:

  • Trình thực thi một luồng: Chỉ có 1 luồng trong nhóm luồng và các tác vụ sẽ được xử lý tuần tự. Tên phương thức “newsinglethreadexecutor ()”

    Nhóm luồng được lưu trong bộ nhớ cache: Sẽ có nhiều luồng trong nhóm luồng và các tác vụ sẽ được xử lý song song. Luồng cũ đã xử lý sẽ được sử dụng lại cho tác vụ mới. Theo mặc định, nếu một luồng không được sử dụng trong 60 giây, luồng đó sẽ bị tắt. Tên phương thức “newcachedthreadpool ()”

    Nhóm chủ đề cố định: Số lượng chủ đề sẽ được cố định (cố định) trong nhóm chủ đề. Nếu một nhiệm vụ mới được giới thiệu và tất cả các luồng đều “bận”, nhiệm vụ sẽ được gửi đến hàng đợi chặn và khi luồng đã hoàn thành nhiệm vụ của mình, các tác vụ trong hàng đợi đó sẽ được xếp lại và xử lý bởi luồng đó. Phương thức “newfixedthreadpool ()”

    Nhóm luồng đã lên lịch: Tương tự như “nhóm luồng được lưu trong bộ nhớ cache”, nhưng có độ trễ giữa các luồng. Phương thức “newschedisedthreadpool ()”

    Nhóm lập lịch đơn luồng: Tương tự như “trình thực thi đơn luồng”, nhưng có độ trễ giữa các luồng. Phương thức “newsinglethreadschedisedexecutor ()”

    3.3. Cách sử dụng dịch vụ Bộ truyền động

    Có một số cách khác nhau để giao nhiệm vụ cho dịch vụ thực thi:

    • thực thi (có thể chạy được)
    • gửi (chạy được)
    • gửi (có thể gọi)
    • invokeany (…)
    • invokeall (…) Dưới đây là cách các phương thức trên được sử dụng và tác dụng của chúng:
    • thực thi (runnable) Phương thức thực thi (runnable) nhận các đối tượng java.lang.runnable và thực thi chúng không đồng bộ. Không thể nhận được kết quả của một thực thi có thể chạy được bằng cách sử dụng phương thức này (không có giá trị gọi lại hoặc trả về khi tác vụ hoàn thành).
    • submit (runnable) Phương thức submit (runnable) cũng chấp nhận một runnable, nhưng nó trả về một đối tượng trong tương lai. Đối tượng tương lai có thể được sử dụng để kiểm tra xem runnable đã thực thi xong chưa.
    • submit (có thể gọi) Phương thức submit (callable) tương tự như submit (runnable) ngoại trừ việc hàm call () của nó yêu cầu giá trị trả về để xác định kết quả nhận được khi tác vụ hoàn thành. Phương thức runnable.run () không thể trả về kết quả. Kết quả của có thể gọi có thể nhận được thông qua đối tượng tương lai được trả về bởi phương thức gửi (có thể gọi)

    Sử dụng phương thức future.get () để nhận kết quả. Lưu ý rằng phương thức này được thực thi đồng bộ (không đồng bộ – nghĩa là sau khi có thể gọi hoàn thành nhiệm vụ của nó, nó sẽ thực thi kết quả trả về).

    • invokeany (tập hợp & lt;? & gt; mở rộng nhiệm vụ có thể gọi & lt; t & gt;) Phương thức invokeany () chấp nhận một có thể gọi hoặc một tập hợp các lớp kế thừa từ một có thể gọi. Việc gọi phương thức này không trả về tương lai mà là kết quả của một trong các đối tượng có thể gọi. Bạn không được đảm bảo bạn sẽ nhận được kết quả gì từ cuộc gọi. Chỉ một trong số chúng được yêu cầu để hoàn thành (tức là tất cả các luồng không bắt buộc phải hoàn thành, chỉ 1 tác vụ hoàn thành phương thức get () để nhận được kết quả.

    Xem thêm: Nhóm Tứ Hành Xung Thìn Tuất Sửu Mùi Và Cách Hóa Giải

    Nếu một trong các nhiệm vụ hoàn thành (hoặc ném một ngoại lệ), phần còn lại của các tác vụ có thể gọi sẽ bị hủy bỏ (bị hủy bỏ).

    Đoạn mã trên sẽ in kết quả trả về từ một đối tượng có thể gọi trong bộ sưu tập. Chạy nó một vài lần và bạn sẽ nhận được kết quả khác nhau.

    • invokeall (bộ sưu tập & lt;? & gt; mở rộng các tác vụ có thể gọi & lt; t & gt;) Phương thức invokeall () gọi tất cả các bảng gọi mà bạn đẩy vào bộ sưu tập. Phương thức này trả về một danh sách các đối tượng trong tương lai (danh sách các hợp đồng tương lai) được trả về từ việc thực thi có thể gọi.

    Hãy nhớ rằng một công việc có thể hoàn thành với một ngoại lệ, điều đó có nghĩa là nó có thể không “thành công” trong việc hoàn thành nhiệm vụ. Không có cách nào để phân biệt các đối tượng trong tương lai.

    3.4. Cách kết thúc dịch vụ thực thi

    • shutdown () Khi bạn đã thêm các tác vụ cần thiết, bạn nên sử dụng phương thức shutdown () để tắt dịch vụ thực thi. Khi bạn gọi phương thức này, điều đó có nghĩa là dịch vụ thực thi sẽ từ chối nhận thêm tác vụ và khi tất cả các tác vụ được thêm trước đó đã được hoàn thành. Sau đó trình thực thi sẽ bị tắt (có nghĩa là tất cả các tác vụ được thêm vào trước khi tắt máy () sẽ được thực thi).

      Sau đây là ví dụ về cách gọi thi hành chương trình thực thi ():

      executeervice.shutdown ();

      shutdownnow () Nếu bạn muốn tắt dịch vụ thực thi ngay lập tức, bạn có thể gọi phương thức shutdownnow (). Thao tác này sẽ cố gắng chặn tất cả các tác vụ cùng một lúc và loại bỏ các tác vụ được xếp hàng đợi nhưng chưa được thực thi. Không có gì đảm bảo rằng các tác vụ đang chạy sẽ bị tắt hoàn toàn, nhưng phương pháp này là cách tốt nhất để tắt chúng.

      Dưới đây là một ví dụ về cách gọi thực thierviceshutdown ():

      executeervice.shutdownnow ();

      awaittermina () Phương thức awaitterminating () thi hành sẽ chặn luồng đã gọi nó cho đến khi dịch vụ thi hành tắt hoàn toàn hoặc một khoảng thời gian nhất định đã trôi qua. Phương thức awaittermina () thường được gọi sau khi shutdown () hoặc shutdownnow () được gọi.

      Sau đây là một ví dụ về cách gọi sự chờ đợi dịch vụ thực thi ():

      Lưu ý: Bạn nên tắt nhóm luồng bằng cách gọi phương thức shutdown () vì chúng tôi không thể chắc chắn rằng máy ảo java có thể tự động làm điều đó

      Trên đây là những hướng dẫn cơ bản của tôi để tạo và sử dụng nhóm luồng trong java. Nếu ứng dụng của bạn cần thực thi nhiều luồng cùng một lúc, thì bạn nên cố gắng tìm hiểu và sử dụng nhóm luồng để có hiệu suất tốt hơn, tiết kiệm thời gian vì không cần tạo luồng mới cho mỗi tác vụ.

      Tham khảo: http://tutorials.jenkov.com/java-util-concurrent/execarieservice.html https://caffinc.github.io/2016/03/simple-threadpool/

      Xem thêm: Opt Out là gì và cấu trúc cụm từ Opt Out trong câu Tiếng Anh

Viết một bình luận