JavaRush /Blog Java /Random-VI /Nghỉ giải lao #113. 5 điều có thể bạn chưa biết về đa luồ...

Nghỉ giải lao #113. 5 điều có thể bạn chưa biết về đa luồng trong Java 10 tiện ích mở rộng JetBrains để giải quyết nợ kỹ thuật

Xuất bản trong nhóm

5 điều có thể bạn chưa biết về đa luồng trong Java

Nguồn: DZone Thread là trái tim của ngôn ngữ lập trình Java. Ngay cả việc chạy chương trình Hello World cũng cần có luồng chính. Nếu cần, chúng ta có thể thêm các luồng khác vào chương trình nếu muốn mã ứng dụng của mình hoạt động tốt hơn và hoạt động hiệu quả hơn. Nếu chúng ta đang nói về một máy chủ web, thì nó xử lý đồng thời hàng trăm yêu cầu. Nhiều chủ đề được sử dụng cho việc này. Nghỉ giải lao #113.  5 điều có thể bạn chưa biết về đa luồng trong Java  10 tiện ích mở rộng JetBrains để giải quyết nợ kỹ thuật - 1Các luồng chắc chắn là hữu ích, nhưng làm việc với chúng có thể gây khó khăn cho nhiều nhà phát triển. Trong bài viết này, tôi sẽ chia sẻ năm khái niệm đa luồng mà các nhà phát triển mới và có kinh nghiệm có thể không biết.

1. Thứ tự chương trình và thứ tự thực hiện không khớp nhau

Khi viết mã, chúng ta giả định rằng nó sẽ thực thi chính xác như cách chúng ta viết. Tuy nhiên, thực tế không phải vậy. Trình biên dịch Java có thể thay đổi thứ tự thực thi để tối ưu hóa nó nếu nó có thể xác định rằng đầu ra sẽ không thay đổi trong mã đơn luồng. Hãy xem đoạn mã sau:
package ca.bazlur.playground;

import java.util.concurrent.Phaser;

public class ExecutionOrderDemo {
    private static class A {
        int x = 0;
    }

    private static final A sharedData1 = new A();
    private static final A sharedData2 = new A();

    public static void main(String[] args) {
        var phaser = new Phaser(3);
        var t1 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l1 = sharedData1;
            var l2 = l1.x;
            var l3 = sharedData2;
            var l4 = l3.x;
            var l5 = l1.x;
            System.out.println("Thread 1: " + l2 + "," + l4 + "," + l5);
        });
        var t2 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l6 = sharedData1;
            l6.x = 3;
            System.out.println("Thread 2: " + l6.x);
        });
        t1.start();
        t2.start();
        phaser.arriveAndDeregister();
    }
}
Mã này có vẻ đơn giản. Chúng tôi có hai phiên bản dữ liệu được chia sẻ ( sharedData1SharedData2 ) sử dụng hai luồng. Khi chúng tôi thực thi mã, chúng tôi mong đợi kết quả đầu ra sẽ như thế này:
Chủ đề 2: 3 Chủ đề 1: 0,0,0
Nhưng nếu bạn chạy mã nhiều lần, bạn sẽ thấy một kết quả khác:
Chủ đề 2: 3 Chủ đề 1: 3,0,3 Chủ đề 2: 3 Chủ đề 1: 0,0,3 Chủ đề 2: 3 Chủ đề 1: 3,3,3 Chủ đề 2: 3 Chủ đề 1: 0,3,0 Chủ đề 2 : 3 Chủ đề 1: 0,3,3
Tôi không nói rằng tất cả các luồng này sẽ phát giống hệt như thế này trên máy của bạn nhưng điều đó hoàn toàn có thể xảy ra.

2. Số lượng luồng Java bị hạn chế

Tạo một thread trong Java thật dễ dàng. Tuy nhiên, điều này không có nghĩa là chúng ta có thể tạo bao nhiêu tùy thích. Số lượng chủ đề có hạn. Chúng ta có thể dễ dàng tìm ra số lượng luồng chúng ta có thể tạo trên một máy cụ thể bằng chương trình sau:
package ca.bazlur.playground;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Playground {
    public static void main(String[] args) {
        var counter = new AtomicInteger();
        while (true) {
            new Thread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            }).start();
        }
    }
}
Chương trình trên rất đơn giản. Nó tạo một luồng trong một vòng lặp và sau đó dừng nó, có nghĩa là luồng này bị vô hiệu hóa để sử dụng trong tương lai nhưng thực hiện lệnh gọi hệ thống và phân bổ bộ nhớ. Chương trình tiếp tục tạo các luồng cho đến khi không thể tạo thêm nữa và sau đó đưa ra một ngoại lệ. Chúng tôi quan tâm đến con số mà chúng tôi sẽ nhận được cho đến khi chương trình đưa ra một ngoại lệ. Trên máy tính của tôi, tôi chỉ có thể tạo 4065 chủ đề.

3. Quá nhiều luồng không đảm bảo hiệu suất tốt hơn

Thật ngây thơ khi tin rằng việc tạo các luồng dễ dàng trong Java sẽ cải thiện hiệu suất ứng dụng. Thật không may, giả định này sai với mô hình đa luồng truyền thống mà Java cung cấp ngày nay. Trên thực tế, quá nhiều luồng có thể làm giảm hiệu suất của ứng dụng. Trước tiên, hãy đặt câu hỏi này: số lượng luồng tối đa tối đa mà chúng ta có thể tạo để tối đa hóa hiệu suất ứng dụng là bao nhiêu? Chà, câu trả lời không đơn giản như vậy. Nó phụ thuộc rất nhiều vào loại công việc chúng ta làm. Nếu chúng ta có nhiều tác vụ độc lập, tất cả đều mang tính tính toán và không chặn bất kỳ tài nguyên bên ngoài nào, thì việc có số lượng lớn luồng sẽ không cải thiện hiệu suất nhiều. Mặt khác, nếu chúng ta có bộ xử lý 8 lõi thì số luồng tối ưu có thể là (8 + 1). Trong trường hợp như vậy, chúng ta có thể dựa vào luồng song song được giới thiệu trong Java 8. Theo mặc định, luồng song song sử dụng nhóm Fork/Join được chia sẻ. Nó tạo ra các luồng bằng số lượng bộ xử lý có sẵn, đủ để chúng hoạt động mạnh mẽ. Việc thêm nhiều luồng hơn vào một công việc sử dụng nhiều CPU mà không có gì bị chặn sẽ không cải thiện hiệu suất. Đúng hơn là chúng ta sẽ lãng phí tài nguyên. Ghi chú. Lý do có thêm một luồng là vì ngay cả một luồng tính toán chuyên sâu đôi khi cũng gây ra lỗi trang hoặc bị treo vì một số lý do khác. (Xem: Java Parallelism in Practice , Brian Goetz, trang 170) Tuy nhiên, giả sử, chẳng hạn, các tác vụ bị ràng buộc I/O. Trong trường hợp này, chúng phụ thuộc vào giao tiếp bên ngoài (ví dụ: cơ sở dữ liệu, các API khác), do đó số lượng luồng lớn hơn sẽ có ý nghĩa. Lý do là khi một thread đang chờ trên Rest API thì các thread khác có thể tiếp tục hoạt động. Bây giờ chúng ta có thể hỏi lại, có bao nhiêu chủ đề là quá nhiều cho một trường hợp như vậy? Phụ thuộc. Không có con số hoàn hảo nào phù hợp với mọi trường hợp. Do đó, chúng tôi phải thực hiện thử nghiệm đầy đủ để tìm ra cách nào phù hợp nhất với khối lượng công việc và ứng dụng cụ thể của mình. Trong kịch bản điển hình nhất, chúng ta thường có một nhóm nhiệm vụ hỗn hợp. Và trong những trường hợp như vậy mọi việc sẽ đi đến hồi kết. Trong cuốn sách “Thực hành đồng thời Java”, Brian Goetz đã đề xuất một công thức mà chúng ta có thể sử dụng trong hầu hết các trường hợp. Số lượng luồng = Số lõi có sẵn * (1 + Thời gian chờ / Thời gian dịch vụ) Thời gian chờ có thể là IO, chẳng hạn như chờ phản hồi HTTP, lấy khóa, v.v. Thời gian phục vụ(Thời gian dịch vụ) là thời gian tính toán, chẳng hạn như xử lý phản hồi HTTP, sắp xếp/không sắp xếp, v.v. Ví dụ: một ứng dụng gọi API và sau đó xử lý nó. Nếu chúng ta có 8 bộ xử lý trên máy chủ ứng dụng, thời gian phản hồi API trung bình là 100 mili giây và thời gian xử lý phản hồi là 20 mili giây thì kích thước luồng lý tưởng sẽ là:
N = 8 * ( 1 + 100/20) = 48
Tuy nhiên, đây là sự đơn giản hóa quá mức; kiểm tra đầy đủ luôn luôn quan trọng để xác định số lượng.

4. Đa luồng không phải là song song

Đôi khi chúng ta sử dụng đa luồng và song song thay thế cho nhau, nhưng điều này không còn hoàn toàn phù hợp nữa. Mặc dù trong Java chúng ta đạt được cả hai điều đó bằng cách sử dụng một luồng, nhưng chúng là hai thứ khác nhau. “Trong lập trình, đa luồng là một trường hợp đặc biệt bất kể các tiến trình đang chạy và tính song song là việc thực hiện đồng thời các phép tính (có thể liên quan). Đa luồng là tương tác với nhiều thứ cùng một lúc. Đồng thời đang làm nhiều việc cùng một lúc.” Định nghĩa trên của Rob Pike đưa ra khá chính xác. Giả sử chúng ta có các nhiệm vụ hoàn toàn độc lập và chúng có thể được tính toán riêng. Trong trường hợp này, các tác vụ này được gọi là song song và có thể được thực thi bằng nhóm Fork/Join hoặc một luồng song song. Mặt khác, nếu chúng ta có nhiều nhiệm vụ, một số nhiệm vụ có thể phụ thuộc vào những nhiệm vụ khác. Cách chúng tôi soạn thảo và cấu trúc được gọi là đa luồng. Nó liên quan đến cấu trúc. Chúng ta có thể muốn thực hiện đồng thời nhiều nhiệm vụ để đạt được một kết quả nhất định mà không nhất thiết phải hoàn thành nhiệm vụ đó nhanh hơn.

5. Project Loom cho phép chúng tôi tạo hàng triệu chủ đề

Ở điểm trước, tôi đã lập luận rằng có nhiều luồng hơn không có nghĩa là hiệu suất ứng dụng được cải thiện. Tuy nhiên, trong kỷ nguyên của microservice, chúng ta tương tác với quá nhiều dịch vụ để có thể hoàn thành bất kỳ công việc cụ thể nào. Trong trường hợp như vậy, hầu hết các luồng vẫn ở trạng thái bị chặn. Mặc dù hệ điều hành hiện đại có thể xử lý hàng triệu socket mở nhưng chúng ta không thể mở nhiều kênh liên lạc vì bị giới hạn bởi số lượng luồng. Nhưng điều gì sẽ xảy ra nếu bạn tạo ra hàng triệu luồng và mỗi luồng sử dụng một ổ cắm mở để giao tiếp với thế giới bên ngoài? Điều này chắc chắn sẽ cải thiện thông lượng ứng dụng của chúng tôi. Để hỗ trợ ý tưởng này, có một sáng kiến ​​trong Java có tên là Project Loom. Sử dụng nó, chúng ta có thể tạo ra hàng triệu luồng ảo. Ví dụ: bằng cách sử dụng đoạn mã sau, tôi có thể tạo 4,5 triệu luồng trên máy của mình.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) {
        var counter = new AtomicInteger();

        // 4_576_279
        while (true) {
            Thread.startVirtualThread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            });
        }
    }
}
Để chạy chương trình này, bạn phải cài đặt Java 18, có thể tải xuống tại đây . Bạn có thể chạy mã bằng lệnh sau: java --source 18 --enable-preview Main.java

10 tiện ích mở rộng JetBrains để giải quyết nợ kỹ thuật

Nguồn: DZone Nhiều nhóm phát triển cảm thấy áp lực rất lớn trong việc đáp ứng thời hạn. Vì điều này, họ thường không có đủ thời gian để sửa chữa và dọn dẹp codebase của mình. Đôi khi trong những tình huống này, nợ kỹ thuật nhanh chóng tích lũy. Tiện ích mở rộng của trình soạn thảo có thể giúp giải quyết vấn đề này. Chúng ta hãy xem 10 tiện ích mở rộng JetBrains tốt nhất để chống lại nợ kỹ thuật (có hỗ trợ Java). Nghỉ giải lao #113.  5 điều có thể bạn chưa biết về đa luồng trong Java  10 tiện ích mở rộng JetBrains để giải quyết nợ kỹ thuật - 2

Công cụ tái cấu trúc và nợ kỹ thuật

1. RefactorInsight

RefactorInsight cải thiện khả năng hiển thị các thay đổi mã trong IDE bằng cách cung cấp thông tin về các lần tái cấu trúc.
  1. Tiện ích mở rộng xác định việc tái cấu trúc trong các yêu cầu hợp nhất.
  2. Marks cam kết có chứa các phép tái cấu trúc.
  3. Giúp xem việc tái cấu trúc của bất kỳ cam kết cụ thể nào được chọn trong tab Nhật ký Git.
  4. Hiển thị lịch sử tái cấu trúc của các lớp, phương thức và trường.

2. Trình theo dõi vấn đề kích thước bước trong IDE

Stepsize là một công cụ theo dõi vấn đề tuyệt vời dành cho các nhà phát triển. Tiện ích mở rộng này giúp các kỹ sư không chỉ tạo TODO và nhận xét mã tốt hơn mà còn ưu tiên nợ kỹ thuật, tái cấu trúc và những thứ tương tự:
  1. Stepsize cho phép bạn tạo và xem các tác vụ bằng mã ngay trong trình chỉnh sửa.
  2. Tìm các vấn đề ảnh hưởng đến tính năng bạn đang làm việc.
  3. Thêm vấn đề vào các lần chạy nước rút của bạn bằng cách sử dụng tích hợp Jira, Asana, Linear, Azure DevOps và GitHub.

3. Luồng mã di tích mới

Relic CodeStream mới là nền tảng cộng tác dành cho nhà phát triển để thảo luận và đánh giá mã. Nó hỗ trợ các yêu cầu kéo từ GitHub, BitBucket và GitLab, quản lý vấn đề từ Jira, Trello, Asana và 9 người khác, đồng thời cung cấp các cuộc thảo luận về mã, gắn kết tất cả lại với nhau.
  1. Tạo, xem xét và hợp nhất các yêu cầu kéo trong GitHub.
  2. Nhận phản hồi về công việc đang tiến hành bằng cách đánh giá mã sơ bộ.
  3. Thảo luận các vấn đề về mã với đồng đội.

TODO và nhận xét

4. Công cụ đánh dấu bình luận

Plugin này cho phép bạn tạo đánh dấu tùy chỉnh các dòng bình luận và từ khóa ngôn ngữ. Plugin cũng có khả năng xác định mã thông báo tùy chỉnh để làm nổi bật các dòng nhận xét.

5. Bình luận tốt hơn

Tiện ích mở rộng Nhận xét tốt hơn giúp bạn tạo nhận xét rõ ràng hơn trong mã của mình. Với tiện ích mở rộng này, bạn sẽ có thể phân loại chú thích của mình thành:
  1. Cảnh báo.
  2. Yêu cầu.
  3. LÀM.
  4. Những khoảnh khắc cơ bản

Lỗi và lỗ hổng bảo mật

6.SonarLint _

SonarLint cho phép bạn khắc phục sự cố về mã trước khi chúng phát sinh. Nó cũng có thể được sử dụng như một công cụ kiểm tra chính tả. SonarLint nêu bật các lỗi và lỗ hổng bảo mật khi bạn viết mã, kèm theo hướng dẫn khắc phục rõ ràng để bạn có thể khắc phục trước khi mã được thực hiện.

7. Lỗi tại chỗ

Plugin SpotBugs cung cấp khả năng phân tích mã byte tĩnh để tìm lỗi trong mã Java từ IntelliJ IDEA. SpotBugs là một công cụ phát hiện lỗi dành cho Java, sử dụng phân tích tĩnh để tìm ra hơn 400 mẫu lỗi như tham chiếu con trỏ null, vòng lặp đệ quy vô hạn, sử dụng sai thư viện Java và bế tắc. SpotBugs có thể xác định hàng trăm lỗi nghiêm trọng trong các ứng dụng lớn (thường là khoảng 1 lỗi trên 1000-2000 dòng câu lệnh thô không có chú thích).

8. Máy quét lỗ hổng Snyk

Trình quét lỗ hổng của Snyk giúp bạn tìm và khắc phục các lỗ hổng bảo mật cũng như các vấn đề về chất lượng mã trong dự án của bạn.
  1. Tìm kiếm và khắc phục các sự cố bảo mật.
  2. Xem danh sách các loại vấn đề khác nhau, được chia thành các loại.
  3. Hiển thị các mẹo khắc phục sự cố.

9. Bộ định vị ký tự có độ rộng bằng không

Plugin này tăng cường khả năng kiểm tra và phát hiện các lỗi khó tìm liên quan đến các ký tự có độ rộng bằng 0 vô hình trong mã nguồn và tài nguyên. Khi sử dụng, hãy đảm bảo rằng kiểm tra “Ký tự unicode có độ rộng bằng 0” được bật.

10.CodeMR _

CodeMR là một công cụ phân tích mã tĩnh và chất lượng phần mềm giúp các công ty phần mềm phát triển mã và chương trình tốt hơn. CodeMR trực quan hóa số liệu mã và thuộc tính chất lượng cấp cao (khớp nối, độ phức tạp, sự gắn kết và kích thước) trong nhiều chế độ xem khác nhau như Cấu trúc gói, Bản đồ cây, Sunburst, Phụ thuộc và Chế độ xem biểu đồ.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION