JavaRush /בלוג Java /Random-HE /אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון
Viacheslav
רָמָה

אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון

פורסם בקבוצה

מבוא

אז, אנחנו יודעים שיש שרשורים ב-Java, עליהם תוכלו לקרוא בסקירה " You Can't Spoil Java with a Thread: Part I - Threads ". יש צורך בחוטים כדי לבצע עבודה בו זמנית. לכן, סביר מאוד שהחוטים יתקשרו איכשהו זה עם זה. בואו נבין איך זה קורה ואילו בקרות בסיסיות יש לנו. אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 1

תְשׁוּאָה

שיטת Thread.yield() מסתורית וממעטת להשתמש בה. ישנן וריאציות רבות לתיאור שלה באינטרנט. עד כדי כך שחלק כותבים על איזה תור של שרשורים, שהשרשור יעבור למטה תוך התחשבות בסדר העדיפויות שלהם. מישהו כותב שהשרשור ישנה את הסטטוס שלו מהפעלה לניתנת להרצה (למרות שאין חלוקה לסטטוסים האלה, וג'אווה לא מבחינה ביניהם). אבל במציאות הכל הרבה יותר לא ידוע ובמובן מסוים פשוט יותר. אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 2בנושא תיעוד השיטה, yieldיש באג " JDK-6416721: (שרשור מפרט) Fix Thread.yield() javadoc ". אם תקראו אותו, ברור שלמעשה השיטה yieldרק מעבירה איזו המלצה למתזמן ה-Java Threads שניתן לתת לשרשור הזה פחות זמן ביצוע. אבל מה באמת יקרה, האם המתזמן ישמע את ההמלצה ומה הוא יעשה באופן כללי תלוי ביישום ה-JVM ומערכת ההפעלה. או אולי מגורמים אחרים. כל הבלבול נבע ככל הנראה מחשיבה מחודשת של ריבוי שרשורים במהלך פיתוח שפת ג'אווה. אתה יכול לקרוא עוד בסקירה " מבוא קצר ל-Java Thread.yield() ".

שינה - חוט הירדמות

חוט עלול להירדם במהלך ביצועו. זהו הסוג הפשוט ביותר של אינטראקציה עם שרשורים אחרים. למערכת ההפעלה שעליה מותקנת המכונה הוירטואלית Java, שבה מבוצע קוד Java, יש מתזמן שרשור משלה, הנקרא Thread Scheduler. הוא זה שמחליט איזה שרשור להפעיל מתי. המתכנת לא יכול לקיים אינטראקציה עם מתזמן זה ישירות מקוד Java, אבל הוא יכול, דרך ה-JVM, לבקש מהמתזמן להשהות את השרשור לזמן מה, כדי "להרדים אותו". אתה יכול לקרוא עוד במאמרים " Thread.sleep() " ו" כיצד פועל ריבוי פתילים ". יתר על כן, אתה יכול לגלות כיצד שרשורים עובדים במערכת ההפעלה של Windows: " פנימיות של שרשור Windows ". עכשיו נראה את זה במו עינינו. בואו נשמור את הקוד הבא בקובץ HelloWorldApp.java:
class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Waked up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
כפי שאתה יכול לראות, יש לנו משימה שמחכה 60 שניות, ולאחר מכן התוכנית מסתיימת. אנחנו קומפילים javac HelloWorldApp.javaומריצים java HelloWorldApp. עדיף להפעיל בחלון נפרד. לדוגמה, ב-Windows זה יהיה כך: start java HelloWorldApp. באמצעות הפקודה jps, אנו מגלים את ה-PID של התהליך ופותחים את רשימת השרשורים באמצעות jvisualvm --openpid pidПроцесса: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 3כפי שניתן לראות, השרשור שלנו נכנס למצב שינה. למעשה, השינה של השרשור הנוכחי יכולה להיעשות בצורה יפה יותר:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Waked up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
בטח שמתם לב שאנחנו מעבדים בכל מקום InterruptedException? בואו נבין למה.

הפסקת שרשור או Thread.interrupt

העניין הוא שבזמן שהשרשור ממתין בחלום, אולי מישהו ירצה להפריע להמתנה הזו. במקרה זה, אנו מטפלים בחריג כזה. זה נעשה לאחר שהשיטה Thread.stopהוכרזה כבלתי מבוטלת, כלומר. מיושן ולא רצוי לשימוש. הסיבה לכך הייתה שכאשר השיטה נקראה, stopהשרשור פשוט "נהרג", וזה היה מאוד בלתי צפוי. לא יכולנו לדעת מתי תיעצר הזרימה, לא יכולנו להבטיח את עקביות הנתונים. תאר לעצמך שאתה כותב נתונים לקובץ ואז הזרם נהרס. לכן החלטנו שיותר הגיוני לא להרוג את הזרימה, אלא להודיע ​​לה שיש להפריע לה. איך להגיב לזה תלוי בזרימה עצמה. פרטים נוספים ניתן למצוא ב"מדוע Thread.stop הוצא משימוש? " בואו נסתכל על דוגמה:
public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
בדוגמה זו, לא נחכה 60 שניות, אלא מיד נדפיס 'מופסק'. הסיבה לכך היא שקראנו לשיטת השרשור interrupt. שיטה זו מגדירה "דגל פנימי הנקרא סטטוס פסיקה". כלומר, לכל שרשור יש דגל פנימי שאינו נגיש ישירות. אבל יש לנו שיטות מקוריות לאינטראקציה עם הדגל הזה. אבל זו לא הדרך היחידה. שרשור יכול להיות בתהליך של ביצוע, לא לחכות למשהו, אלא פשוט לבצע פעולות. אבל זה יכול לספק שהם ירצו להשלים אותו בשלב מסוים בעבודתו. לדוגמה:
public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			//Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
בדוגמה למעלה, אתה יכול לראות שהלולאה whileתפעל עד שהחוט יופסק חיצונית. הדבר שחשוב לדעת על הדגל isInterrupted הוא שאם נתפוס אותו InterruptedException, הדגל isInterruptedמתאפס, ואז isInterruptedהוא יחזור שקר. ישנה גם שיטה סטטית במחלקה Thread שחלה רק על השרשור הנוכחי - Thread.interrupted() , אבל שיטה זו מאפסת את הדגל ל-false! תוכל לקרוא עוד בפרק " הפרעת שרשור ".

הצטרף - ממתין להשלמת שרשור נוסף

הסוג הפשוט ביותר של המתנה הוא המתנה להשלמת שרשור נוסף.
public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
בדוגמה זו, השרשור החדש יישן למשך 5 שניות. יחד עם זאת, החוט הראשי ימתין עד שחוט השינה יתעורר ויסיים את עבודתו. אם תסתכל דרך JVisualVM, מצב השרשור ייראה כך: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 4הודות לכלי ניטור, תוכל לראות מה קורה עם השרשור. השיטה joinדי פשוטה, כי זו פשוט שיטה עם קוד ג'אווה שפועלת waitבזמן שה-thread עליו היא נקראת חי. ברגע שהפתיל מת (בסיומו), ההמתנה נפסקת. זה כל הקסם של השיטה join. לכן, בואו נעבור לחלק המעניין ביותר.

קונספט מוניטור

ב-multithreading יש דבר כזה מוניטור. באופן כללי, המילה מוניטור מתורגמת מלטינית כ"משגיח" או "משגיח". במסגרת מאמר זה ננסה לזכור את המהות, ולמי שרוצה אני מבקש לצלול לחומר מהקישורים לפרטים. בואו נתחיל את המסע שלנו עם מפרט שפת Java, כלומר עם JLS: " 17.1. סינכרון ". זה אומר את הדברים הבאים: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 5מסתבר שלצורך סנכרון בין שרשורים, Java משתמשת במנגנון מסוים שנקרא "מוניטור". לכל אובייקט יש צג המשויך אליו, ופתילים יכולים לנעול אותו או לפתוח אותו. לאחר מכן, נמצא מדריך הדרכה באתר אורקל: " נעילות פנימיות וסנכרון ". מדריך זה מסביר שהסנכרון ב-Java בנוי סביב ישות פנימית המכונה נעילה פנימית או נעילת צג. לעתים קרובות מנעול כזה נקרא פשוט "צג". אנו גם רואים שוב שלכל אובייקט בג'אווה יש נעילה פנימית הקשורה אליו. אתה יכול לקרוא " Java - מנעולים פנימיים וסנכרון ". לאחר מכן, חשוב להבין כיצד ניתן לשייך אובייקט ב-Java למוניטור. לכל אובייקט בג'אווה יש כותרת - מעין מטא-נתונים פנימיים שאינם זמינים למתכנת מהקוד, אלא שהמכונה הוירטואלית צריכה לעבוד עם אובייקטים בצורה נכונה. כותרת האובייקט כוללת MarkWord שנראית כך: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

מאמר מהאבר שימושי מאוד כאן: " אבל איך עובד ריבוי הליכי שרשור? חלק א': סנכרון ." למאמר זה כדאי להוסיף תיאור מתוך סיכום בלוק המשימות מ-JDK bugtaker: " JDK-8183909 ". אתה יכול לקרוא את אותו הדבר ב" JEP-8183909 ". אז, ב-Java, מוניטור משויך לאובייקט והשרשור יכול לחסום את השרשור הזה, או שהם גם אומרים "קבל נעילה". הדוגמה הפשוטה ביותר:
public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
לכן, באמצעות מילת המפתח, synchronizedהשרשור הנוכחי (בו שורות הקוד הללו מבוצעות) מנסה להשתמש במוניטור המשויך לאובייקט objectו"לקבל נעילה" או "ללכוד את הצג" (האפשרות השנייה אפילו עדיפה). אם אין ויכוח על המוניטור (כלומר אף אחד אחר לא רוצה לסנכרן על אותו אובייקט), ג'אווה יכולה לנסות לבצע אופטימיזציה שנקראת "נעילה מוטה". הכותרת של האובייקט ב-Mark Word תכיל את התג המתאים ורשומה לאיזה חוט מחובר המוניטור. זה מפחית את התקורה בעת לכידת הצג. אם הצג כבר נקשר לחוט אחר בעבר, אז הנעילה הזו לא מספיקה. ה-JVM עובר לסוג הנעילה הבא - נעילה בסיסית. הוא משתמש בפעולות השוואה והחלפה (CAS). יחד עם זאת, הכותרת ב-Mark Word כבר לא מאחסנת את Mark Word עצמו, אלא משתנה קישור לאחסון שלו + התגית כך שה-JVM יבין שאנחנו משתמשים בנעילה בסיסית. אם יש ויכוח על המוניטור של מספר שרשורים (אחד תפס את המוניטור, והשני ממתין לשחרור המוניטור), אז התגית ב-Mark Word משתנה, ו-Mark Word מתחיל לאחסן הפניה למוניטור בתור אובייקט - ישות פנימית כלשהי של ה-JVM. כפי שצוין ב-JEP, במקרה זה, נדרש מקום באזור הזיכרון Native Heap לאחסון ישות זו. הקישור למיקום האחסון של ישות פנימית זו ימוקם באובייקט Mark Word. לפיכך, כפי שאנו רואים, המוניטור הוא באמת מנגנון להבטחת סנכרון של גישה של שרשורים מרובים למשאבים משותפים. ישנם מספר יישומים של מנגנון זה שה-JVM עובר ביניהם. לכן, לשם הפשטות, כשמדברים על מוניטור, אנחנו בעצם מדברים על מנעולים. אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 7

מסונכרן וממתין לפי נעילה

המושג של מוניטור, כפי שראינו בעבר, קשור קשר הדוק למושג "גוש סנכרון" (או, כפי שהוא נקרא גם, קטע קריטי). בואו נסתכל על דוגמה:
public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized (lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized (lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print("  " + i);
		}
		System.out.println(" ...");
	}
}
כאן, השרשור הראשי שולח תחילה את המשימה לשרשור חדש, ולאחר מכן "לוכד" מיד את המנעול ומבצע איתו פעולה ארוכה (8 שניות). כל הזמן הזה, המשימה לא יכולה להיכנס לבלוק לביצוע שלה synchronized, כי המנעול כבר תפוס. אם חוט לא יכול להשיג נעילה, הוא ימתין לו ליד הצג. ברגע שהוא יקבל אותו, הוא ימשיך בביצוע. כאשר חוט יוצא מהצג, הוא משחרר את הנעילה. ב-JVisualVM זה ייראה כך: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 8כפי שאתה יכול לראות, הסטטוס ב-JVisualVM נקרא "מוניטור" מכיוון שהשרשור חסום ואינו יכול לתפוס את המוניטור. אתה יכול גם לברר את מצב השרשור בקוד, אך השם של מצב זה אינו עולה בקנה אחד עם מונחי JVisualVM, למרות שהם דומים. במקרה זה, th1.getState()הלולאה forתחזיר BLOCKED , כי בזמן שהלולאה פועלת, הצג lockתפוס mainעל ידי החוט, והחוט th1חסום ואינו יכול להמשיך לעבוד עד שהנעילה תוחזר. בנוסף לחסימות סנכרון, ניתן לסנכרן שיטה שלמה. לדוגמה, שיטה מהמחלקה HashTable:
public synchronized int size() {
	return count;
}
ביחידת זמן אחת, שיטה זו תתבצע על ידי חוט אחד בלבד. אבל אנחנו צריכים מנעול, נכון? כן אני צריך את זה. במקרה של שיטות אובייקט, המנעול יהיה this. יש דיון מעניין בנושא זה: " האם יש יתרון להשתמש בשיטה סינכרונית במקום בלוק מסונכרן? ". אם המתודה היא סטטית, אז הנעילה לא תהיה this(שכן עבור מתודה סטטית לא יכול להיות this), אלא אובייקט המחלקה (לדוגמה, Integer.class).

מחכה ומחכה על המוניטור. השיטות הודע והודיע ​​כל

ל-Thread יש שיטת המתנה נוספת, שמחוברת למוניטור. שלא כמו sleepו join, לא ניתן לקרוא לזה סתם. ושמו הוא wait. השיטה מבוצעת waitעל האובייקט שעל הצג שלו אנו רוצים להמתין. בוא נראה דוגמה:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // task будет ждать, пока его не оповестят через lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // После оповещения нас мы будем ждать, пока сможем взять лок
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // Ждём и после этого забираем себе лок, оповещаем и отдаём лок
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
ב-JVisualVM זה ייראה כך: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 10כדי להבין איך זה עובד, עליך לזכור ששיטות waitמתייחסות notifyל java.lang.Object. זה נראה מוזר ששיטות הקשורות לשרשור נמצאות ב- Object. אבל כאן טמונה התשובה. כפי שאנו זוכרים, לכל אובייקט ב-Java יש כותרת. הכותרת מכילה מידע שירותים שונים, כולל מידע על הצג - נתונים על מצב הנעילה. וכזכור, לכל אובייקט (כלומר לכל מופע) יש שיוך עם ישות JVM פנימית הנקראת נעילה פנימית, הנקראת גם מוניטור. בדוגמה שלמעלה, המשימה מתארת ​​שאנו נכנסים לבלוק הסנכרון בצג המשויך ל lock. אם אפשר להשיג נעילה על צג זה, אז wait. השרשור שיבצע משימה זו ישחרר את המוניטור lock, אך יצטרף לתור השרשורים הממתינים להתראה בצג lock. תור השרשורים הזה נקרא WAIT-SET, מה שמשקף בצורה נכונה יותר את המהות. זה יותר סט מאשר תור. השרשור mainיוצר שרשור חדש עם משימת המשימה, מתחיל אותו וממתין 3 שניות. זה מאפשר, במידה רבה של הסתברות, לשרשור חדש לתפוס את המנעול לפני השרשור mainולעמוד בתור על הצג. לאחר מכן החוט mainעצמו נכנס לבלוק הסנכרון lockומבצע הודעה על השרשור על המסך. לאחר שליחת ההודעה, השרשור mainמשחרר את הצג lock, והשרשור החדש (שהמתין בעבר) lockממשיך לפעול לאחר ההמתנה לשחרור הצג. אפשר לשלוח הודעה רק לאחד השרשורים ( notify) או לכל השרשורים בתור בו זמנית ( notifyAll). אתה יכול לקרוא עוד ב"הבדל בין notify() ל-notifyAll() ב-Java ". חשוב לציין שסדר ההודעה תלוי ביישום JVM. אתה יכול לקרוא עוד ב"איך לפתור הרעבה עם הודע והודיע ​​הכל? ". ניתן לבצע סנכרון מבלי לציין אובייקט. ניתן לעשות זאת כאשר לא קטע קוד נפרד מסונכרן, אלא שיטה שלמה. לדוגמה, עבור שיטות סטטיות המנעול יהיה אובייקט המחלקה (שהושג באמצעות .class):
public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
מבחינת שימוש במנעולים, שתי השיטות זהות. אם השיטה אינה סטטית, הסנכרון יתבצע לפי הנוכחי instance, כלומר לפי this. אגב, קודם אמרנו שבאמצעות השיטה getStateניתן לקבל סטטוס של שרשור. אז הנה שרשור שנמצא בתור על ידי המוניטור, המצב יהיה WAITING או TIMED_WAITING אם השיטה waitציינה מגבלת זמן המתנה. אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 11

מחזור חיים של חוט

כפי שראינו, הזרימה משנה את מעמדו במהלך החיים. בעצם, שינויים אלה הם מחזור החיים של החוט. כאשר שרשור נוצר זה עתה, יש לו את הסטטוס NEW. במצב זה, הוא עדיין לא התחיל ומתזמן ה-Java Threads עדיין לא יודע כלום על השרשור החדש. כדי שמתזמן השרשורים יידע על שרשור, עליך להתקשר ל- thread.start(). אז השרשור יעבור למצב RUNNABLE. יש הרבה סכימות שגויות באינטרנט שבהן מופרדים מצבי ריצה ומצב ריצה. אבל זו טעות, כי... Java לא מבדילה בין סטטוס "מוכן להפעלה" ו"פועל". כאשר שרשור חי אך אינו פעיל (לא ניתן להרצה), הוא נמצא באחד משני מצבים:
  • חסום - ממתין לכניסה לקטע מוגן, כלומר. אל synchonizedהבלוק.
  • WAITING - מחכה לשרשור נוסף על סמך תנאי. אם התנאי נכון, מתזמן השרשור מתחיל את השרשור.
אם שרשור ממתין לפי הזמן, הוא במצב TIMED_WAITING. אם השרשור אינו פועל יותר (הושלם בהצלחה או עם חריגה), הוא עובר למצב TERMINATED. כדי לברר את מצבו של חוט (מצבו), משתמשים בשיטה getState. ל-Threads יש גם שיטה isAliveשמחזירה true אם השרשור לא מסתיים.

LockSupport וחניה חוט

מאז Java 1.6 היה מנגנון מעניין בשם LockSupport . אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 12מחלקה זו משייכת "הרשאה" או הרשאה לכל שרשור שמשתמש בה. קריאת השיטה parkחוזרת מיד אם קיים אישור זמין, תופסת את אותו אישור במהלך השיחה. אחרת הוא חסום. הקריאה לשיטה unparkמאפשרת היתר אם הוא אינו זמין כבר. יש רק הרשאה אחת. ב-Java API, LockSupportמסוים Semaphore. בואו נסתכל על דוגמה פשוטה:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Просим разрешение и ждём, пока не получим его
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
הקוד הזה יחכה לנצח כי לסמפור יש כעת 0 היתר. וכאשר קוראים לקוד acquire(כלומר, בקש הרשאה), השרשור ממתין עד שהוא מקבל רשות. מכיוון שאנו ממתינים, אנו מחויבים לעבד אותו InterruptedException. מעניין, סמפור מיישם מצב חוט נפרד. אם נסתכל ב-JVisualVM, נראה שהמדינה שלנו היא לא Wait, אלא Park. אתה לא יכול להרוס את ג'אווה עם שרשור: חלק ב' - סנכרון - 13בואו נסתכל על דוגמה נוספת:
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            //Запаркуем текущий поток
            System.err.println("Will be Parked");
            LockSupport.park();
            // Как только нас распаркуют - начнём действовать
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());

        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
סטטוס השרשור יהיה WAITING, אבל JVisualVM מבחין waitבין מ - synchronizedו- . למה זה כל כך חשוב ? בואו נפנה שוב ל-Java API ונסתכל על Thread State WAITING . כפי שאתה יכול לראות, יש רק שלוש דרכים להיכנס לזה. 2 דרכים - זה ו . והשלישית היא . מנעולים ב-Java בנויים על אותם עקרונות ומייצגים כלים ברמה גבוהה יותר. בואו ננסה להשתמש באחד. בואו נסתכל, למשל, ב : parkLockSupportLockSupportwaitjoinLockSupportLockSupportReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
כמו בדוגמאות קודמות, כאן הכל פשוט. lockמחכה שמישהו ישחרר משאב. אם נסתכל ב-JVisualVM, נראה שהשרשור החדש יוחנה עד mainשהשרשור ייתן לו את הנעילה. אתה יכול לקרוא עוד על מנעולים כאן: " תכנות מרובה הליכי ב-Java 8. חלק שני. סנכרון גישה לאובייקטים הניתנים לשינוי " ו- " Java Lock API. תיאוריה ודוגמה לשימוש ." כדי להבין טוב יותר את היישום של מנעולים, כדאי לקרוא על Phazer בסקירה הכללית " Phaser Class ". ואם כבר מדברים על מסנכרנים שונים, עליך לקרוא את המאמר על Habré " Java.util.concurrent.* Reference Synchronizers ".

סה"כ

בסקירה זו, בדקנו את הדרכים העיקריות שבהן שרשורים מתקשרים ב-Java. חומר נוסף: #ויאצ'סלב
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION