CodeGym /בלוג Java /Random-HE /עדיף ביחד: Java וכיתה Thread. חלק ב' - סנכרון
John Squirrels
רָמָה
San Francisco

עדיף ביחד: Java וכיתה Thread. חלק ב' - סנכרון

פורסם בקבוצה

מבוא

אז, אנחנו יודעים שלג'אווה יש חוטים. אתה יכול לקרוא על כך בסקירה שכותרתה Better together: Java and the Thread class. חלק א' - חוטי הוצאה להורג . יש צורך בחוטים לביצוע עבודה במקביל. זה גורם לסבירות גבוהה שהשרשורים יתקשרו איכשהו זה עם זה. בואו נראה איך זה קורה ואילו כלים בסיסיים יש לנו. עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 1

תְשׁוּאָה

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

לִישׁוֹן

חוט יכול ללכת לישון במהלך ביצועו. זהו הסוג הקל ביותר של אינטראקציה עם שרשורים אחרים. למערכת ההפעלה שמפעילה את המכונה הוירטואלית Java שמריצה את קוד ה-Java שלנו יש מתזמן חוטים משלה . הוא מחליט איזה שרשור להתחיל ומתי. מתכנת לא יכול לקיים אינטראקציה עם מתזמן זה ישירות מקוד Java, רק דרך ה-JVM. הוא או היא יכולים לבקש מהמתזמן להשהות את השרשור לזמן מה, כלומר להרדים אותו. אתה יכול לקרוא עוד במאמרים הבאים: Thread.sleep() ואיך Multithreading עובד . אתה יכול גם לבדוק איך שרשורים עובדים במערכות הפעלה של 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("Woke 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: עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 3כפי שאתה יכול לראות, לשרשור שלנו יש כעת את הסטטוס "שינה". למעשה, יש דרך אלגנטית יותר לעזור לשרשור שלנו יש חלומות מתוקים:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
שמתם לב שאנחנו מטפלים InterruptedExceptionבכל מקום? בואו נבין למה.

Thread.interrupt()

העניין הוא שבזמן שרשור מחכה/ישן, אולי מישהו ירצה להפריע. במקרה זה, אנו מטפלים ב- InterruptedException. מנגנון זה נוצר לאחר שהשיטה Thread.stop()הוכרזה Deprecated, כלומר מיושנת ולא רצויה. הסיבה הייתה שכאשר 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()יחזור false. למחלקה 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, אז זה ייראה כך: עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 4הודות לכלי ניטור, אתה יכול לראות מה קורה עם השרשור. השיטה joinדי פשוטה, כי זו רק שיטה עם קוד Java שפועלת wait()כל עוד השרשור עליו הוא נקרא חי. ברגע שהחוט מת (כשהוא מסיים את עבודתו), ההמתנה נקטעת. וזה כל הקסם של join()השיטה. אז בואו נעבור לדבר המעניין ביותר.

צג

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

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

להלן מאמר JavaWorld שהוא מאוד שימושי: כיצד המכונה הוירטואלית של Java מבצעת סנכרון שרשורים . יש לשלב מאמר זה עם התיאור מקטע "סיכום" של הבעיה הבאה במערכת מעקב באגים של JDK: 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"\כדי לקבל/לרכוש את הנעילה. אם אף אחד אחר לא מתמודד על הצג (כלומר אף אחד אחר לא מריץ קוד מסונכרן באמצעות אותו אובייקט), אז Java עשויה לנסות לבצע אופטימיזציה הנקראת "נעילה מוטה". תגית רלוונטית ורשומה לגבי איזה חוט מחזיק במנעול המוניטור מתווספים למילת הסימון בכותרת האובייקט. זה מפחית את התקורה הנדרשת לנעילת צג. אם הצג היה בעבר בבעלות חוט אחר, אז נעילה כזו אינה מספיקה. ה-JVM עובר לסוג הנעילה הבא: "נעילה בסיסית". הוא משתמש בפעולות השוואה והחלפה (CAS). יתרה מכך, מילת הסימן של כותרת האובייקט עצמה כבר לא מאחסנת את מילת הסימון, אלא הפניה למקום שבו היא מאוחסנת, והתג משתנה כך שה-JVM יבין שאנו משתמשים בנעילה בסיסית. אם מספר שרשורים מתחרים (מתחרים) על מוניטור (אחד רכש את המנעול, ושנייה מחכה לשחרור המנעול), אז התגית במילה סימון משתנה, ומילה סימון מאחסנת כעת הפניה למוניטור כאובייקט - ישות פנימית כלשהי של ה-JVM. כפי שצוין ב-JDK Enchancement Proposal (JEP), מצב זה דורש מקום באזור הזיכרון Native Heap על מנת לאחסן ישות זו. ההתייחסות למיקום הזיכרון של ישות פנימית זו תישמר במילת הסימן של כותרת האובייקט. לפיכך, מוניטור הוא באמת מנגנון לסנכרון גישה למשאבים משותפים בין שרשורים מרובים. ה-JVM עובר בין מספר יישומים של מנגנון זה. אז לשם הפשטות, כשמדברים על המוניטור, אנחנו בעצם מדברים על מנעולים. עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 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 זה נראה כך: עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 8כפי שניתן לראות ב-JVisualVM, הסטטוס הוא "מוניטור", כלומר השרשור חסום ואינו יכול לקחת את המוניטור. אתה יכול גם להשתמש בקוד כדי לקבוע את סטטוס השרשור, אך שמות הסטטוס שנקבעו כך אינם תואמים לשמות המשמשים ב-JVisualVM, למרות שהם דומים. במקרה זה, th1.getState()ההצהרה בלולאת for תחזיר BLOCKED , כי כל עוד הלולאה פועלת, lockהמוניטור של האובייקט תפוס על ידי ה- mainthread, וה- th1thread נחסם ולא יכול להמשיך עד לשחרור המנעול. בנוסף לבלוקים מסונכרנים, ניתן לסנכרן שיטה שלמה. לדוגמה, הנה שיטה מהכיתה HashTable:
public synchronized int size() {
	return count;
}
שיטה זו תתבצע על ידי שרשור אחד בלבד בכל זמן נתון. האם אנחנו באמת צריכים את המנעול? כן, אנחנו צריכים את זה. במקרה של שיטות מופע, האובייקט "זה" (האובייקט הנוכחי) פועל כמנעול. יש כאן דיון מעניין בנושא זה: האם יש יתרון בשימוש בשיטה סינכרונית במקום בלוק סינכרוני? . אם השיטה היא סטטית, אזי המנעול לא יהיה האובייקט "זה" (מכיוון שאין אובייקט "זה" למתודה סטטית), אלא אובייקט Class (לדוגמה, ) Integer.class.

המתן (מחכה למוניטור). שיטות notify() ו-notifyAll().

למחלקה Thread יש שיטת המתנה נוספת המשויכת למוניטור. שלא כמו sleep()ו join(), לא ניתן לקרוא לשיטה זו פשוט. שמו הוא wait(). השיטה waitנקראת על האובייקט המשויך למוניטור שאנו רוצים לחכות לו. בוא נראה דוגמה:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
ב-JVisualVM, זה נראה כך: עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 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. קרא עוד כאן: כיצד לפתור הרעבה עם notify and notifyAll? . ניתן לבצע סנכרון מבלי לציין אובייקט. אתה יכול לעשות זאת כאשר שיטה שלמה מסונכרנת במקום גוש קוד בודד. לדוגמה, עבור שיטות סטטיות, המנעול יהיה אובייקט Class (שמושג באמצעות .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()ציינה פסק זמן. עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

מחזור חיי חוט

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

LockSupport וחניה חוט

החל מ-Java 1.6, הופיע מנגנון מעניין בשם LockSupport . עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 12מחלקה זו משייכת "אישור" לכל שרשור שמשתמש בו. קריאה לשיטה park()חוזרת מיד אם ההיתר זמין, תוך צורך בהיתר. אחרת, זה חוסם. קריאה לשיטה unparkהופכת את ההיתר לזמין אם הוא עדיין לא זמין. יש רק אישור אחד. תיעוד Java עבור 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) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
הקוד הזה תמיד ימתין, כי עכשיו לסמפור יש 0 אישורים. וכאשר acquire()נקרא בקוד (כלומר לבקש את ההיתר), השרשור ממתין עד שיקבל את ההיתר. מכיוון שאנו מחכים, עלינו לטפל InterruptedException. מעניין שהסמפור מקבל מצב חוט נפרד. אם נסתכל ב-JVisualVM, נראה שהמדינה היא לא "חכה", אלא "פארק". עדיף ביחד: Java וכיתה Thread.  חלק ב' - סנכרון - 13בואו נסתכל על דוגמה נוספת:
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            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המפתח לבין parkהמחלקה LockSupport. למה זה LockSupportכל כך חשוב? אנו פונים שוב לתיעוד Java ומסתכלים על מצב השרשור WAITING . כפי שאתה יכול לראות, יש רק שלוש דרכים להיכנס לזה. שתיים מהדרכים הללו הן wait()ו join(). והשלישי הוא LockSupport. ב-Java, ניתן לבנות מנעולים גם על LockSupport ולהציע כלים ברמה גבוהה יותר. בואו ננסה להשתמש באחד. לדוגמה, תסתכל על ReentrantLock:
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 StampedLocks לעומת ReadWriteLocks ו-Synchronized and Lock API ב-Java. כדי להבין טוב יותר כיצד מנעולים מיושמים, כדאי לקרוא על Phaser במאמר זה: מדריך ל-Java Phaser . ואם כבר מדברים על מסנכרנים שונים, אתה חייב לקרוא את המאמר של DZone על The Java Synchronizers.

סיכום

בסקירה זו, בחנו את הדרכים העיקריות שבהן שרשורים מתקשרים ב-Java. חומר נוסף: עדיף ביחד: Java וכיתה Thread. חלק א' - חוטי ביצוע טוב יותר ביחד: ג'אווה ומחלקת ה-Thread. חלק שלישי - אינטראקציה טובה יותר ביחד: Java ומחלקת Thread. חלק IV — ניתן להתקשרות, עתיד וחברים טובים יותר ביחד: Java וכיתה Thread. חלק V - Executor, ThreadPool, Fork/Join Better יחד: Java ומחלקת Thread. חלק ו' - אש!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION