Java Regex Thread có an toàn không?


104

Tôi có một hàm sử dụng Pattern#compilevà một Matcherđể tìm kiếm danh sách các chuỗi cho một mẫu.

Chức năng này được sử dụng trong nhiều chủ đề. Mỗi luồng sẽ có một mẫu duy nhất được chuyển đến Pattern#compilekhi luồng được tạo. Số luồng và mẫu là động, có nghĩa là tôi có thể thêm nhiều Patterns và luồng hơn trong quá trình cấu hình.

Tôi có cần đặt một synchronizehàm này nếu nó sử dụng regex không? Regex trong java thread có an toàn không?

Câu trả lời:


132

, từ tài liệu Java API cho lớp Mẫu

Các phiên bản của lớp (Mẫu) này là bất biến và an toàn để sử dụng bởi nhiều luồng đồng thời. Các phiên bản của lớp Matcher không an toàn cho việc sử dụng như vậy.

Nếu bạn đang xem mã tập trung vào hiệu suất, hãy thử đặt lại phiên bản Matcher bằng phương thức reset (), thay vì tạo phiên bản mới. Điều này sẽ đặt lại trạng thái của cá thể Matcher, làm cho nó có thể sử dụng được cho hoạt động regex tiếp theo. Trên thực tế, chính trạng thái được duy trì trong cá thể Matcher là nguyên nhân khiến nó không an toàn khi truy cập đồng thời.


17
Đối tượng mẫu là luồng an toàn, nhưng compile()phương thức có thể không. Đã có hai hoặc ba lỗi trong nhiều năm khiến quá trình biên dịch không thành công trong môi trường đa luồng. Tôi khuyên bạn nên thực hiện biên dịch trong một khối được đồng bộ hóa.
Alan Moore

4
Có, đã có lỗi đồng thời được nêu ra trong lớp Mẫu và lời khuyên của bạn về truy cập đồng bộ hóa được đánh giá cao. Tuy nhiên, các nhà phát triển ban đầu của lớp Pattern dự định làm cho lớp Pattern trở thành luồng an toàn và đó là hợp đồng mà bất kỳ lập trình viên Java nào cũng có thể dựa vào. Thành thật mà nói, tôi muốn có các biến cục bộ của chuỗi và chấp nhận lần truy cập hiệu suất tối thiểu hơn là dựa vào hành vi an toàn của chuỗi theo hợp đồng (trừ khi tôi đã xem mã). Như họ nói "Phân luồng thì dễ, đồng bộ hóa chính xác mới khó".
Vineet Reynolds

1
Lưu ý rằng nguồn của "Mẫu" nằm trong bản phân phối Oracle JDK (Theo oracle.com/technetwork/java/faq-141681.html#A14 : "Bản thân Java 2 SDK, Standard Edition chứa một tệp có tên là src.zip chứa mã nguồn cho các lớp công khai trong gói java ") để người ta có thể xem nhanh.
David Tonhofer

@DavidTonhofer Tôi nghĩ JDK mới nhất của chúng tôi có thể có mã không có lỗi chính xác, nhưng vì các tệp .class trung gian của Java có thể được giải thích trên bất kỳ nền tảng nào bởi bất kỳ máy ảo tương thích nào, bạn không thể chắc chắn rằng các bản sửa lỗi đó tồn tại trong thời gian chạy đó. Tất nhiên, hầu hết thời gian bạn biết máy chủ đang chạy phiên bản nào, nhưng thật tẻ nhạt khi kiểm tra từng phiên bản.
TWiStErRob

12

An toàn luồng với các biểu thức chính quy trong Java

TÓM LƯỢC:

API biểu thức chính quy Java đã được thiết kế để cho phép chia sẻ một mẫu đã biên dịch duy nhất qua nhiều hoạt động đối sánh.

Bạn có thể gọi Pattern.matcher () một cách an toàn trên cùng một mẫu từ các luồng khác nhau và sử dụng đồng thời các trình so khớp một cách an toàn. Pattern.matcher () an toàn để tạo các trình so khớp mà không cần đồng bộ hóa. Mặc dù phương thức không được đồng bộ hóa, bên trong lớp Pattern, một biến dễ thay đổi được gọi là biên dịch luôn được đặt sau khi xây dựng một mẫu và đọc khi bắt đầu lệnh gọi tới matcher (). Điều này buộc bất kỳ luồng nào tham chiếu đến Mẫu phải "nhìn thấy" chính xác nội dung của đối tượng đó.

Mặt khác, bạn không nên chia sẻ Matcher giữa các chuỗi khác nhau. Hoặc ít nhất, nếu bạn đã từng làm, bạn nên sử dụng đồng bộ hóa rõ ràng.


2
@akf, BTW, bạn nên lưu ý rằng đó là một trang web thảo luận (giống như trang này). Tôi sẽ xem xét bất cứ điều gì bạn tìm thấy ở đó không tốt hơn hoặc tệ hơn thông tin mà bạn tìm thấy ở đây (tức là, đó không phải là Lời Chân Thật Từ James Gosling).
Bob Cross

3

Trong khi bạn cần nhớ rằng sự an toàn của chuỗi cũng phải tính đến mã xung quanh, bạn có vẻ là người may mắn. Thực tế là quẹt được tạo ra bằng cách sử dụng của Pattern khớp phương pháp nhà máy và thiếu nhà xây dựng công cộng là một dấu hiệu tích cực. Tương tự như vậy, bạn sử dụng phương thức biên dịch tĩnh để tạo Mẫu bao trùm .

Vì vậy, trong ngắn hạn, nếu bạn làm một cái gì đó như ví dụ:

Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();

bạn sẽ làm khá tốt.

Theo dõi ví dụ mã để rõ ràng hơn: lưu ý rằng ví dụ này ngụ ý mạnh mẽ rằng Matcher do đó được tạo là một chuỗi cục bộ với Mẫu và thử nghiệm. Tức là, bạn không nên để Matcher được tạo ra với bất kỳ chuỗi nào khác.

Thành thật mà nói, đó là rủi ro của bất kỳ câu hỏi an toàn luồng nào. Thực tế là bất kỳnào cũng có thể trở thành luồng không an toàn nếu bạn cố gắng đủ nhiều. May mắn thay, có những cuốn sách tuyệt vời dạy chúng ta rất nhiều cách mà chúng ta có thể làm hỏng mã của mình. Nếu chúng ta tránh xa những sai lầm đó, chúng ta sẽ giảm đáng kể xác suất gặp vấn đề về luồng của chính mình.


@Jason S: địa phương luồng là một cách rất đơn giản để đạt được an toàn cho luồng ngay cả khi mã nội bộ không an toàn cho luồng. Nếu chỉ có một phương thức có thể truy cập một phương thức cụ thể tại một thời điểm, thì bạn đã thực thi an toàn luồng bên ngoài.
Bob Cross

1
ok, vì vậy bạn chỉ nói rằng việc tạo lại một mẫu từ một chuỗi tại thời điểm sử dụng, tốt hơn là lưu trữ nó để có hiệu quả, trước rủi ro đối phó với các vấn đề đồng thời? tôi sẽ cấp cho bạn điều đó. Tôi đã nhầm lẫn với câu đó về các phương pháp nhà máy và các nhà xây dựng công cộng, điều đó có vẻ giống như một con cá trích đỏ với chủ đề này.
Jason S

@Jason S, không, các phương pháp gốc và thiếu các hàm tạo là một số cách mà bạn có thể giảm mối đe dọa ghép nối với các luồng khác. Nếu cách duy nhất bạn có thể lấy Matcher đi cùng với Mẫu của tôi là thông qua p.matcher (), thì không ai khác có thể tác động phụ đến Matcher của tôi. Tuy nhiên, tôi vẫn có thể tự gây rắc rối cho chính mình: nếu tôi có một phương thức công khai trả về Matcher đó, thì một luồng khác có thể truy cập vào nó và gây tác dụng phụ cho nó. Nói tóm lại, đồng thời là khó (bằng BẤT CỨ ngôn ngữ nào).
Bob Cross

2

Xem nhanh mã cho Matcher.javahiển thị một loạt các biến thành viên bao gồm văn bản đang được so khớp, mảng cho nhóm, một vài chỉ mục để duy trì vị trí và một vài chỉ số booleancho trạng thái khác. Tất cả điều này chỉ ra một trạng thái Matchersẽ không hoạt động tốt nếu được nhiều người truy cập Threads. JavaDoc cũng vậy :

Các phiên bản của lớp này không an toàn để sử dụng bởi nhiều luồng đồng thời.

Đây chỉ là một vấn đề nếu, như @Bob Cross chỉ ra, bạn không cho phép sử dụng tài khoản của mình Matchertrong các trường hợp riêng biệt Thread. Nếu bạn cần làm điều này và bạn nghĩ rằng đồng bộ hóa sẽ là một vấn đề đối với mã của bạn, một tùy chọn bạn có là sử dụng ThreadLocalđối tượng lưu trữ để duy trì một Matcherchuỗi hoạt động.


1

Tóm lại, bạn có thể sử dụng lại (giữ trong các biến tĩnh) (các) Mẫu đã biên dịch và yêu cầu họ cung cấp cho bạn các Đối sánh mới khi cần thiết để xác thực các tệp regex đó dựa trên một số chuỗi

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Validation helpers
 */
public final class Validators {

private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$";

private static Pattern email_pattern;

  static {
    email_pattern = Pattern.compile(EMAIL_PATTERN);
  }

  /**
   * Check if e-mail is valid
   */
  public static boolean isValidEmail(String email) { 
    Matcher matcher = email_pattern.matcher(email);
    return matcher.matches();
  }

}

xem http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/ (gần cuối) về mẫu RegEx được sử dụng ở trên để xác thực e-mail ( trong trường hợp nó không phù hợp với những người cần xác thực e-mail vì nó được đăng ở đây)


3
Cảm ơn đã đăng câu trả lời của bạn! Hãy đảm bảo đọc kỹ Câu hỏi thường gặp về Tự quảng cáo . Ai đó có thể nhìn thấy câu trả lời này và bài đăng blog được liên kết đến và nghĩ rằng bạn đã đăng bài đăng trên blog đơn thuần để bạn có thể liên kết đến nó từ đây.
Andrew Barber

2
Tại sao phải bận tâm với static {}? Bạn có thể nội dòng khởi tạo biến đó và thực hiện Pattern final.
TWiStErRob

1
Tôi thứ hai quan điểm của TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);tốt hơn.
Christophe Roussy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.