Java Regex Thread ปลอดภัยหรือไม่


104

ฉันมีฟังก์ชันที่ใช้Pattern#compileและMatcherค้นหารายการสตริงสำหรับรูปแบบ

ฟังก์ชันนี้ใช้ในหลายเธรด แต่ละเธรดจะมีรูปแบบเฉพาะที่ส่งผ่านไปยังPattern#compileเวลาที่สร้างเธรด จำนวนเธรดและรูปแบบเป็นแบบไดนามิกซึ่งหมายความว่าฉันสามารถเพิ่มPatterns และเธรดได้มากขึ้นในระหว่างการกำหนดค่า

ฉันต้องใส่synchronizeฟังก์ชันนี้หรือไม่หากใช้ regex regex ใน java thread ปลอดภัยหรือไม่?

คำตอบ:


133

ใช่จากเอกสาร Java API สำหรับคลาส Pattern

อินสแตนซ์ของคลาส (Pattern) นี้ไม่เปลี่ยนรูปและปลอดภัยสำหรับการใช้งานโดยเธรดหลายเธรดพร้อมกัน อินสแตนซ์ของคลาส Matcher ไม่ปลอดภัยสำหรับการใช้งานดังกล่าว

หากคุณกำลังดูโค้ดที่เน้นประสิทธิภาพให้ลองรีเซ็ตอินสแตนซ์ Matcher โดยใช้เมธอด reset () แทนการสร้างอินสแตนซ์ใหม่ สิ่งนี้จะรีเซ็ตสถานะของอินสแตนซ์ Matcher ทำให้สามารถใช้งานได้สำหรับการดำเนินการ regex ถัดไป ในความเป็นจริงมันเป็นสถานะที่คงไว้ในอินสแตนซ์ Matcher ที่รับผิดชอบว่าจะไม่ปลอดภัยสำหรับการเข้าถึงพร้อมกัน


17
วัตถุรูปแบบเป็นเธรดที่ปลอดภัย แต่compile()วิธีนี้อาจไม่เป็นเช่นนั้น ในช่วงหลายปีที่ผ่านมามีข้อบกพร่องสองหรือสามจุดที่ทำให้การคอมไพล์ล้มเหลวในสภาพแวดล้อมแบบมัลติเธรด ฉันอยากจะแนะนำให้ทำการคอมไพล์ในบล็อกที่ซิงโครไนซ์
Alan Moore

4
ใช่มีข้อบกพร่องเกิดขึ้นพร้อมกันในคลาส Pattern และคำแนะนำของคุณเกี่ยวกับการเข้าถึงแบบซิงโครไนซ์ได้รับการชื่นชม อย่างไรก็ตามนักพัฒนาดั้งเดิมของคลาส Pattern ตั้งใจที่จะทำให้คลาส Pattern เป็นเธรดที่ปลอดภัยและนั่นคือสัญญาที่โปรแกรมเมอร์ Java ทุกคนควรจะสามารถพึ่งพาได้ เพื่อให้ตรงไปตรงมาฉันควรมีเธรดตัวแปรในระบบและยอมรับประสิทธิภาพการทำงานที่น้อยที่สุดแทนที่จะพึ่งพาพฤติกรรมที่ปลอดภัยของเธรดตามสัญญา (เว้นแต่ฉันจะเห็นรหัส) ตามที่พวกเขากล่าวว่า "การทำเธรดนั้นง่ายการซิงโครไนซ์ที่ถูกต้องนั้นยาก"
Vineet Reynolds

1
โปรดทราบว่าแหล่งที่มาของ "Pattern" อยู่ในการแจกจ่าย Oracle JDK (อ้างอิงจากoracle.com/technetwork/java/faq-141681.html#A14 : "Java 2 SDK, Standard Edition มีไฟล์ชื่อ src.zip มีซอร์สโค้ดสำหรับคลาสพับลิกในแพ็คเกจ java ") เพื่อให้สามารถมองเห็นตัวเองได้อย่างรวดเร็ว
David Tonhofer

@DavidTonhofer ฉันคิดว่า JDK ล่าสุดของเราอาจมีรหัสที่ไม่มีข้อผิดพลาดที่ถูกต้อง แต่เนื่องจากไฟล์. class ระดับกลางของ Java สามารถตีความบนแพลตฟอร์มใดก็ได้โดย VM ที่เข้ากันได้ใด ๆ คุณจึงไม่แน่ใจว่ามีการแก้ไขเหล่านั้นอยู่ในรันไทม์นั้น แน่นอนว่าส่วนใหญ่แล้วคุณจะรู้ว่าเซิร์ฟเวอร์กำลังทำงานอยู่เวอร์ชันใด แต่การตรวจสอบทุกเวอร์ชันเป็นเรื่องน่าเบื่อ
TWiStErRob

12

ความปลอดภัยของเธรดด้วยนิพจน์ทั่วไปใน Java

สรุป:

API นิพจน์ทั่วไปของ Java ได้รับการออกแบบมาเพื่อให้สามารถใช้รูปแบบที่คอมไพล์ร่วมกันในการดำเนินการจับคู่หลายรายการ

คุณสามารถเรียก Pattern.matcher ()บนรูปแบบเดียวกันจากเธรดที่แตกต่างกันได้อย่างปลอดภัยและใช้ตัวจับคู่พร้อมกันได้อย่างปลอดภัย Pattern.matcher ()ปลอดภัยในการสร้างตัวจับคู่โดยไม่ต้องซิงโครไนซ์ แม้ว่าเมธอดจะไม่ซิงโครไนซ์ภายในคลาส Pattern แต่ตัวแปรระเหยที่เรียกว่าคอมไพล์จะถูกตั้งค่าเสมอหลังจากสร้างรูปแบบและอ่านที่จุดเริ่มต้นของ call to matcher () สิ่งนี้บังคับให้เธรดใด ๆ ที่อ้างถึง Pattern เพื่อ "ดู" เนื้อหาของวัตถุนั้นอย่างถูกต้อง

ในทางกลับกันคุณไม่ควรแชร์ Matcher ระหว่างเธรดต่างๆ หรืออย่างน้อยถ้าคุณเคยทำคุณควรใช้การซิงโครไนซ์อย่างชัดเจน


2
@akf, BTW คุณควรสังเกตว่านั่นคือไซต์สนทนา (คล้าย ๆ กับไซต์นี้) ฉันจะพิจารณาอะไรก็ตามที่คุณพบว่าไม่มีข้อมูลที่ดีกว่าหรือแย่ไปกว่าที่คุณพบที่นี่ (กล่าวคือไม่ใช่คำที่แท้จริงจาก James Gosling)
Bob Cross

3

ในขณะที่คุณต้องจำไว้ว่าความปลอดภัยของเธรดจะต้องคำนึงถึงรหัสรอบข้างด้วยเช่นกันดูเหมือนว่าคุณจะโชคดี ความจริงที่ว่าMatchersถูกสร้างขึ้นโดยใช้วิธีการจับคู่ของ Pattern และไม่มีตัวสร้างสาธารณะเป็นสัญญาณเชิงบวก ในทำนองเดียวกันคุณใช้รวบรวมวิธีการแบบคงที่จะสร้างครอบคลุมรูปแบบ

ดังนั้นในระยะสั้นหากคุณทำบางสิ่งดังตัวอย่าง:

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

คุณน่าจะทำได้ดี

ติดตามตัวอย่างโค้ดเพื่อความชัดเจน: โปรดทราบว่าตัวอย่างนี้มีนัยอย่างยิ่งว่า Matcher ที่สร้างขึ้นนั้นเป็นเธรดโลคัลที่มี Pattern และการทดสอบ กล่าวคือคุณไม่ควรเปิดเผย Matcher ที่สร้างขึ้นด้วยเหตุนี้กับเธรดอื่น ๆ

ตรงไปตรงมานั่นคือความเสี่ยงของคำถามเกี่ยวกับความปลอดภัยของเธรด ความจริงก็คือรหัสใด ๆสามารถทำให้เธรดไม่ปลอดภัยได้หากคุณพยายามอย่างเต็มที่ โชคดีที่มีหนังสือที่ยอดเยี่ยม ที่สอนวิธีต่างๆมากมายที่เราสามารถทำลายรหัสของเราได้ หากเราอยู่ห่างจากข้อผิดพลาดเหล่านั้นเราจะลดความน่าจะเป็นของปัญหาการทำเธรดได้อย่างมาก


@ Jason S: ตำแหน่งของเธรดเป็นวิธีที่ตรงไปตรงมามากในการบรรลุความปลอดภัยของเธรดแม้ว่ารหัสภายในจะไม่ปลอดภัยก็ตาม หากมีเพียงวิธีเดียวเท่านั้นที่สามารถเข้าถึงวิธีการใดวิธีหนึ่งได้ในแต่ละครั้งคุณได้บังคับใช้ความปลอดภัยของเธรดจากภายนอก
Bob Cross

1
โอเคคุณแค่บอกว่าการสร้างแพทเทิร์นขึ้นมาใหม่จากสตริง ณ จุดใช้งานดีกว่าการจัดเก็บให้มีประสิทธิภาพเสี่ยงต่อการจัดการกับปัญหาพร้อมกันหรือไม่? ฉันจะให้คุณ ฉันสับสนกับประโยคนั้นเกี่ยวกับวิธีการของโรงงานและผู้สร้างสาธารณะซึ่งดูเหมือนว่าเป็นปลาเฮอริ่งสีแดงที่ไม่มีหัวข้อนี้
Jason S

@ Jason S ไม่วิธีการของโรงงานและการขาดตัวสร้างเป็นวิธีการบางอย่างที่คุณสามารถลดการคุกคามของการเชื่อมต่อกับเธรดอื่น ๆ หากวิธีเดียวที่คุณจะได้รับ Matcher ที่เข้ากับ Pattern ของฉันคือผ่านทาง p.matcher () ไม่มีใครสามารถให้ผลข้างเคียงกับ Matcher ของฉันได้ อย่างไรก็ตามฉันยังสามารถสร้างปัญหาให้กับตัวเองได้: หากฉันมีวิธีสาธารณะที่ส่งคืน Matcher นั้นเธรดอื่นจะเข้ามาที่มันและมีผลข้างเคียง ในระยะสั้นการทำงานพร้อมกันนั้นยาก (ในภาษาใดก็ได้)
Bob Cross

2

ดูโค้ดอย่างรวดเร็วเพื่อMatcher.javaแสดงตัวแปรสมาชิกจำนวนมากรวมถึงข้อความที่กำลังจับคู่อาร์เรย์สำหรับกลุ่มดัชนีสองสามรายการสำหรับรักษาตำแหน่งและอีกสองสามรายการbooleanสำหรับสถานะอื่น ๆ ทั้งหมดนี้ชี้ไปที่ stateful ที่จะไม่ประพฤติดีถ้าเข้าถึงได้โดยหลายMatcher Threadsเพื่อไม่JavaDoc :

อินสแตนซ์ของคลาสนี้ไม่ปลอดภัยสำหรับการใช้เธรดพร้อมกันหลายเธรด

นี้เป็นเพียงปัญหาถ้าเป็น @Bob ครอสชี้ให้คุณออกไปจากทางของคุณที่จะช่วยให้การใช้งานของคุณMatcherแยกThreads หากคุณจำเป็นต้องทำเช่นนี้และคุณคิดว่าการซิงโครไนซ์จะเป็นปัญหาสำหรับโค้ดของคุณตัวเลือกที่คุณมีคือการใช้ThreadLocalอ็อบเจ็กต์การจัดเก็บเพื่อรักษาMatcherเธรดที่ใช้งานได้


1

เพื่อสรุปผลคุณสามารถใช้ซ้ำ (เก็บไว้ในตัวแปรคงที่) รูปแบบที่คอมไพล์แล้วและบอกให้พวกเขาให้ Matchers ใหม่แก่คุณเมื่อจำเป็นเพื่อตรวจสอบความถูกต้องของ regex pattens กับสตริงบางตัว

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();
  }

}

โปรดดูhttp://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/ (ใกล้สุด) เกี่ยวกับรูปแบบ RegEx ที่ใช้ด้านบนสำหรับการตรวจสอบความถูกต้องของอีเมล ( ในกรณีที่ไม่ตรงกับที่ต้องการสำหรับการตรวจสอบอีเมลตามที่โพสต์ไว้ที่นี่)


3
ขอบคุณสำหรับการโพสต์คำตอบของคุณ! โปรดอ่านคำถามที่พบบ่อยเกี่ยวกับการส่งเสริมตนเองอย่างละเอียด อาจมีคนเห็นคำตอบนี้และบล็อกโพสต์ที่เชื่อมโยงและคิดว่าคุณโพสต์บล็อกโพสต์เพียงเพื่อให้คุณสามารถเชื่อมโยงได้จากที่นี่
Andrew Barber

2
รำคาญด้วยทำไมstatic {}? คุณสามารถอินไลน์การเริ่มต้นตัวแปรนั้นและสร้างได้Pattern finalเช่นกัน
TWiStErRob

1
ฉันสองความคิดเห็นของ TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);ดีกว่า
Christophe Roussy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.