בטיחות תהליך בקבצי סינגלטון ב-Java

הַ- Singleton הוּא אחד מ־הַתְבִּיעוֹת לַיְצִירָה הַנִּפְרָצוֹת בַּיּוֹצֵר דֶפְסֵיָנָה לְהַגְבִּיל אֶת הֶעָצִים שֶׁנּוֹצְרוּ עַל יְדֵי הַיַּפְלֵיקָצִיוֹת. אִם אַתָּה מַשְׁתִּמֵשׁ בּוֹ בִּסְבִיבָת מַסְרֵקֶת מֻרְבֶּית, אָז בִּטְחוּת הַטְרֵאד שֶׁל כְּתֵר הַסִינְגְּלְטוֹן חָשׁוּבָה מְאוֹד. בְּיַישׁוּם הַעוֹלָמִי, מָשְׁאִית כְּמוֹ חְיבוּרֵי מְסוּמִים לְמַעֲרָכוֹת מֵידָע אִנְפֹּרְמָצִיה (EIS) הֵם מוּגְבָּלִים וְצָרִיכִים לְהִשְׁתַּמֵּשׁ בְּחָכְמָה כְּדֵי לִמְנוֹעַ כָּל חֲסָרוֹן מַשָׂאָה. כְּדי לַעֲשׂוֹת זֶה, אֲנַחְנוּ יָכוֹלִים לְיַישֵׁם אֶת הַתְּבִיעָה הַמּוּפְעֶלֶת. אֲנַחְנוּ יָכוֹלִים לִיצוֹר מַחְזְקֵי מִחְסוֹם לַמִּשְׂאָב וְלַהַגְבִּיל אֶת מְסַפַּר הָעֲצוּמוֹת שֶׁנוֹצְרוּ בַּזְמַן רְיצָה לְאֶחָד.

סִנְגְּלְטוֹן בְּטָרֵד בְּטוּחַ בִּג'אווָה

בַּכְּלָל, אֲנוּ עוֹקְבִים אַחֲרֵי הַשְׁלָבִים הָאֲשֵׁר לִיצוֹר מֵחֶסוֹם יָחִיד:

  1. לִיצוֹר אֶת הַבּוֹנֵה הַפְּרָטִי כְּדֵי לַמְנוֹעַ יְצִירָת אוֹבִייקט חָדָש עִם אוֹפֶרֶטוֹר חָדָש.
  2. לְהַצִטִּין אֶת הַאֵילָם הַסְטָטִי הַפְּרָטִי שֶׁל הַמַּחְלָקָה הַשֶׁקֶּד.
  3. ספק שיטה סטטית ציבורית שתחזיר משתנה מופע של מחלקה יחידנית. אם המשתנה לא מאותחל אז תאתחל אותו או פשוט תחזיר את משתנה המופע.

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

package com.journaldev.designpatterns;

public class ASingleton {

	private static ASingleton instance = null;

	private ASingleton() {
	}

	public static ASingleton getInstance() {
		if (instance == null) {
			instance = new ASingleton();
		}
		return instance;
	}

}

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

איך להשיג בטיחות תהליכים במחלקה יחידנית?

ישנם שלושה דרכים שבהן אנו יכולים להשיג בטיחות תהליכים.

  1. יצירת משתנה המופע בעת טעינת המחלקה.
    יתרונות:
  • בטיחות תהליכים ללא סינכרון
  • קל ליישם

חסרונות:

  • יצירה מוקדמת של משאב שעלול שלא להינצל ביישום.
  • היישום הלקוח אינו יכול להעביר ארגומנט כלשהו, לכן אין אפשרות להשתמש בו מחדש. לדוגמה, יש לנו מחלקה יחידנית גנרית לחיבור למסד נתונים שבה היישום הלקוח מספק תכונות שרת מסד הנתונים.
  1. סנכרון של השיטה getInstance().
    יתרונות:
  • Thread safety מובטח.
  • אפליקציית הלקוח יכולה להעביר פרמטרים
  • האתחול העצל הושג

חסרונות:

  • ביצוע איטי עקבות להפחתת העומס על הנעילה.
  • סנכרון מיותר שאינו נדרש לאחר שמשתנה המופע מאותחל.
  1. השתמש בבלוק מסונכרן בתוך לולאת if ומשתנה וולטיל
    יתרונות:
  • Thread safety מובטח
  • אפליקציית הלקוח יכולה להעביר ארגומנטים
  • האתחול העצל הושג
  • עומס סנכרון נמוך וניתן להחיל אותו רק על כמה אשראיות ראשונות כאשר המשתנה הוא null.

חסרונות:

  • תנאי if נוסף

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

package com.journaldev.designpatterns;

public class ASingleton {

	private static volatile ASingleton instance;
	private static Object mutex = new Object();

	private ASingleton() {
	}

	public static ASingleton getInstance() {
		ASingleton result = instance;
		if (result == null) {
			synchronized (mutex) {
				result = instance;
				if (result == null)
					instance = result = new ASingleton();
			}
		}
		return result;
	}

}

משתנה המקומי result נראה מיותר. אך, הוא כאן כדי לשפר את ביצועי הקוד שלנו. במקרים בהם המופע כבר אותחל (רוב הזמן), שדה ה- volatile נגיש רק פעם אחת (בגלל "return result;" במקום "return instance;"). זה יכול לשפר את ביצועי השיטה בכמעט 25 אחוזים. אם יש לך דרכים טובות יותר להשיג זאת או אם בטיחות התהליך נפגעת במימוש למעלה, אנא הער ושתף אותנו כולנו.

טיפ בונוס

String אינו מועמד טוב מאוד לשימוש עם מילת המפתח synchronized. זה כי הם מאוחסנים בבריכת מחרוזות ואנו לא רוצים לנעול מחרוזת שעשויה להימצא בשימוש על ידי קוד אחר. לכן אני משתמש במשתנה Object. למידע נוסף על סנכרון ובטיחות של תהליכים ב-Java. thread safety in java.

ניתן לבדוק דוגמאות נוספות ב-Java ממאגר הקוד שלנו ב־GitHub Repository.

Source:
https://www.digitalocean.com/community/tutorials/thread-safety-in-java-singleton-classes