מבוא ל- ThreadLocal בג'אווה

1. סקירה כללית

במאמר זה נבחן את ה- ThreadLocal לבנות מתוך java.lang חֲבִילָה. זה נותן לנו את האפשרות לאחסן נתונים באופן אינדיבידואלי עבור השרשור הנוכחי - ופשוט לעטוף אותם בתוך סוג מיוחד של אובייקט.

2. ThreadLocal ממשק API

ה TheadLocal מבנה מאפשר לנו לאחסן נתונים שיהיו נגיש בלבד על ידי חוט מסוים.

בואו נגיד שאנחנו רוצים שיהיה לנו מספר שלם ערך שיצורף לשרשור הספציפי:

ThreadLocal threadLocalValue = ThreadLocal חדש ();

לאחר מכן, כאשר אנו רוצים להשתמש בערך זה משרשור אנו צריכים רק לקרוא a לקבל() אוֹ מַעֲרֶכֶת() שיטה. במילים פשוטות, אנחנו יכולים לחשוב את זה ThreadLocal מאחסן נתונים בתוך מפה - עם השרשור כמפתח.

בשל עובדה זו, כאשר אנו קוראים לקבל() שיטה על threadLocalValue, נקבל מספר שלם ערך לשרשור המבקש:

threadLocalValue.set (1); תוצאה שלמה = threadLocalValue.get ();

אנו יכולים לבנות מופע של ThreadLocal באמצעות withInitial () שיטה סטטית והעברת ספק אליה:

ThreadLocal threadLocal = ThreadLocal.withInitial (() -> 1);

כדי להסיר את הערך מה- ThreadLocal, אנחנו יכולים לקרוא לְהַסִיר() שיטה:

threadLocal.remove ();

כדי לראות כיצד להשתמש ב- ThreadLocal כראוי, ראשית, נסתכל על דוגמה שאינה משתמשת ב- ThreadLocalואז נכתוב את הדוגמה שלנו כדי למנף את המבנה הזה.

3. שמירת נתוני משתמשים במפה

בואו ניקח בחשבון תוכנית שצריכה לאחסן את המשתמש הספציפי הֶקשֵׁר נתונים לכל מזהה משתמש נתון:

Context בכיתה ציבורית {שם משתמש פרטי מחרוזת; הקשר ציבורי (שם מחרוזת) {this.userName = userName; }}

אנו רוצים שיהיה חוט אחד לכל מזהה משתמש. ניצור a SharedMapWithUserContext כיתה המיישמת את ניתן לרוץ מִמְשָׁק. היישום ב לָרוּץ() השיטה קוראת למסד נתונים כלשהו דרך ה- UserRepository כיתה שמחזירה א הֶקשֵׁר חפץ נתון תעודת זהות של משתמש.

לאחר מכן, אנו שומרים את ההקשר ב ConcurentHashMap נפתח על ידי תעודת זהות של משתמש:

מחלקה ציבורית SharedMapWithUserContext מיישמת Runnable {public static Map userContextPerUserId = new ConcurrentHashMap (); userId שלם פרטי; UserRepository פרטי UserRepository = UserRepository חדש (); @Override הפעלה בטלנית ציבורית () {String userName = userRepository.getUserNameForUserId (userId); userContextPerUserId.put (userId, Context new (userName)); } // קונסטרוקטור סטנדרטי}

אנו יכולים לבדוק את הקוד שלנו בקלות על ידי יצירה והתחלה של שני שרשורים לשניים שונים userIds וטוען שיש לנו שני רשומות ב- userContextPerUserId מַפָּה:

SharedMapWithUserContext firstUser = SharedMapWithUserContext חדש (1); SharedMapWithUserContext secondUser = SharedMapWithUserContext חדש (2); שרשור חדש (FirstUser) .start (); שרשור חדש (secondUser) .start (); assertEquals (SharedMapWithUserContext.userContextPerUserId.size (), 2);

4. אחסון נתוני משתמשים ב ThreadLocal

אנו יכולים לשכתב את הדוגמה שלנו כדי לאחסן את המשתמש הֶקשֵׁר מופע באמצעות ThreadLocal. לכל חוט יש משלו ThreadLocal למשל.

כשמשתמש ThreadLocal, אנחנו צריכים להיות זהירים מאוד כי כל ThreadLocal מופע משויך לשרשור מסוים. בדוגמה שלנו, יש לנו חוט ייעודי לכל אחד מהם תעודת זהות של משתמש, והחוט הזה נוצר על ידינו, ולכן יש לנו שליטה מלאה עליו.

ה לָרוּץ() השיטה תביא את הקשר המשתמש ותאחסן אותו ב- ThreadLocal משתנה באמצעות מַעֲרֶכֶת() שיטה:

מחלקה ציבורית ThreadLocalWithUserContext מיישם Runnable {פרטי סטטי ThreadLocal userContext = ThreadLocal חדש (); userId שלם פרטי; UserRepository פרטי UserRepository = UserRepository חדש (); @Override הפעלה בטלנית ציבורית () {String userName = userRepository.getUserNameForUserId (userId); userContext.set (הקשר חדש (userName)); System.out.println ("הקשר פתיל עבור userId נתון:" + userId + "הוא:" + userContext.get ()); } // קונסטרוקטור סטנדרטי}

אנו יכולים לבדוק זאת על ידי התחלת שני שרשורים אשר יבצעו את הפעולה עבור נתון תעודת זהות של משתמש:

ThreadLocalWithUserContext firstUser = חדש ThreadLocalWithUserContext (1); ThreadLocalWithUserContext secondUser = ThreadLocalWithUserContext חדש (2); שרשור חדש (FirstUser) .start (); שרשור חדש (secondUser) .start ();

לאחר הפעלת קוד זה נראה על הפלט הסטנדרטי את זה ThreadLocal נקבע לכל חוט נתון:

הקשר ההליך עבור userId נתון: 1 הוא: Context {userNameSecret = '18a78f8e-24d2-4abf-91d6-79eaa198123f'} הקשר ההליך עבור user given id: 2 הוא: Context {userNameSecret = 'e19f6a0a-253e-423e-8b2b-bcacff}

אנו יכולים לראות שלכל אחד מהמשתמשים יש משלו הֶקשֵׁר.

5. ThreadLocalבריכות הברגה

ThreadLocal מספק ממשק API קל לשימוש להגבלת ערכים מסוימים לכל שרשור. זוהי דרך סבירה להשגת בטיחות חוטים ב- Java. למרות זאת, עלינו להיות זהירים במיוחד כאשר אנו משתמשים ThreadLocals ובריכות חוטים יחד.

כדי להבין טוב יותר את האזהרה האפשרית, נבחן את התרחיש הבא:

  1. ראשית, היישום שואל שרשור מהבריכה.
  2. ואז הוא מאחסן כמה ערכים מוגבלים בחוטים בערכים הנוכחיים ThreadLocal.
  3. לאחר סיום הביצוע הנוכחי, היישום מחזיר את השרשור המושאל לבריכה.
  4. כעבור זמן מה, היישום לווה את אותו שרשור כדי לעבד בקשה אחרת.
  5. מכיוון שהיישום לא ביצע את הניקויים הדרושים בפעם האחרונה, הוא עשוי להשתמש בו שוב ThreadLocal נתונים לבקשה החדשה.

זה עלול לגרום לתוצאות מפתיעות ביישומים בו זמנית.

אחת הדרכים לפתור בעיה זו היא להסיר כל אחת באופן ידני ThreadLocal ברגע שסיימנו להשתמש בזה. מכיוון שגישה זו זקוקה לבדיקות קפדניות של קוד, היא עלולה להיות נוטה לשגיאות.

5.1. הרחבת ה- ThreadPoolExecutor

כפי שמתברר, אפשר להרחיב את ThreadPoolExecutor בכיתה ולספק יישום וו מותאם אישית עבור beforeExecute () ו afterExecute () שיטות. מאגר החוטים יקרא beforeExecute () שיטה לפני הפעלת משהו באמצעות החוט המושאל. מצד שני, זה יקרא afterExecute () לאחר ביצוע ההיגיון שלנו.

לכן, אנו יכולים להאריך את ThreadPoolExecutor מחלקה והסר את ThreadLocal נתונים ב afterExecute () שיטה:

מחלקה ציבורית ThreadLocalAwareThreadPool מרחיב את ThreadPoolExecutor {@Override מוגן ריק לאחר ביצוע (Runnable r, Throwable t) {// שיחה להסיר בכל ThreadLocal}}

אם אנו מגישים את בקשותינו ליישום זה של שירות ExecutorService, אז אנחנו יכולים להיות בטוחים ששימוש ThreadLocal ובריכות חוטים לא יכניסו סכנות בטיחות ליישום שלנו.

6. מסקנה

במאמר מהיר זה, הסתכלנו על ThreadLocal לִבנוֹת. יישמנו את ההיגיון שמשתמש בו ConcurrentHashMap ששותף בין האשכולות לאחסון ההקשר המשויך לפרט מסוים תעודת זהות של משתמש. לאחר מכן, כתבנו מחדש את הדוגמה שלנו למינוף ThreadLocal לאחסון נתונים המשויכים לפרט מסוים תעודת זהות של משתמש ועם חוט מסוים.

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found