JavaRush /وبلاگ جاوا /Random-FA /شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - ...
Viacheslav
مرحله

شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی

در گروه منتشر شد

معرفی

بنابراین، ما می دانیم که موضوعاتی در جاوا وجود دارد که می توانید در بررسی " You Can't Spoil Java with a Thread: Part I - Threads " در مورد آنها بخوانید. برای انجام کار به طور همزمان به نخ ها نیاز است. بنابراین، به احتمال زیاد نخ ها به نحوی با یکدیگر تعامل خواهند داشت. بیایید بفهمیم که چگونه این اتفاق می افتد و چه کنترل های اساسی داریم. شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 1

بازده

متد Thread.yield() مرموز است و به ندرت استفاده می شود. تغییرات زیادی در توضیحات آن در اینترنت وجود دارد. تا جایی که برخی در مورد نوعی صف نخ ها می نویسند که در آن موضوع با در نظر گرفتن اولویت های آنها به سمت پایین حرکت می کند. شخصی می نویسد که موضوع وضعیت خود را از حالت در حال اجرا به قابل اجرا تغییر می دهد (اگرچه هیچ تقسیمی به این وضعیت ها وجود ندارد و جاوا بین آنها تمایز قائل نمی شود). اما در واقعیت، همه چیز بسیار ناشناخته تر و به یک معنا ساده تر است. شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 2در مبحث مستندسازی روش، yieldباگ " JDK-6416721: (رشته مشخصات) Fix Thread.yield() javadoc " وجود دارد. اگر آن را بخوانید، واضح است که در واقع این روش yieldفقط توصیه هایی را به زمانبندی رشته جاوا می دهد که می توان زمان اجرای کمتری به این رشته داد. اما اینکه واقعاً چه اتفاقی می‌افتد، اینکه آیا زمان‌بند توصیه را می‌شنود و به طور کلی چه کاری انجام می‌دهد به اجرای JVM و سیستم عامل بستگی دارد. یا شاید از برخی عوامل دیگر. همه سردرگمی ها به احتمال زیاد به دلیل تجدید نظر در مورد چند رشته ای در طول توسعه زبان جاوا بود. می توانید در بررسی " معرفی مختصر به جاوا Thread.yield() " بیشتر بخوانید.

خواب - به خواب رفتن نخ

یک نخ ممکن است در حین اجرای آن به خواب رود. این ساده ترین نوع تعامل با موضوعات دیگر است. سیستم عاملی که ماشین مجازی جاوا بر روی آن نصب شده است، جایی که کد جاوا در آن اجرا می شود، زمانبندی رشته خود را دارد که Thread Scheduler نام دارد. این اوست که تصمیم می گیرد کدام رشته را در چه زمانی اجرا کند. برنامه‌نویس نمی‌تواند مستقیماً از طریق کد جاوا با این زمان‌بند تعامل داشته باشد، اما می‌تواند از طریق JVM از زمان‌بندی‌کننده بخواهد تا موضوع را برای مدتی مکث کند و آن را به حالت Sleep قرار دهد. می توانید در مقاله های " Thread.sleep() " و " چگونه Multithreading کار می کند " بیشتر بخوانید. علاوه بر این، می توانید نحوه کار رشته ها در سیستم عامل ویندوز را بیابید: " Internals of Windows Thread ". حالا با چشمان خود خواهیم دید. بیایید کد زیر را در یک فایل ذخیره کنیم 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. بهتر است در یک پنجره جداگانه راه اندازی شود. به عنوان مثال، در ویندوز به این صورت خواهد بود: start java HelloWorldApp. با استفاده از دستور jps، PID فرآیند را می یابیم و لیست رشته ها را با استفاده از jvisualvm --openpid pidПроцесса: شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 3همانطور که می بینید، موضوع ما وارد وضعیت Sleeping شده است. در واقع، خواباندن موضوع فعلی را می توان به زیبایی بیشتری انجام داد:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Waked up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
احتمالاً متوجه شده اید که ما همه جا پردازش می کنیم InterruptedException؟ بیایید بفهمیم چرا.

قطع کردن یک موضوع یا Thread.interrupt

مسئله این است که در حالی که نخ در خواب منتظر است، ممکن است کسی بخواهد این انتظار را قطع کند. در این مورد، ما چنین استثنایی را مدیریت می کنیم. Thread.stopاین کار پس از اعلام منسوخ شدن روش انجام شد ، یعنی. قدیمی و نامطلوب برای استفاده دلیل این امر این بود که وقتی روش فراخوانی شد، stopموضوع به سادگی "کشته شد" که بسیار غیرقابل پیش بینی بود. ما نمی‌توانستیم بدانیم چه زمانی جریان متوقف می‌شود، نمی‌توانیم ثبات داده‌ها را تضمین کنیم. تصور کنید که داده ها را روی یک فایل می نویسید و سپس جریان از بین می رود. بنابراین، ما تصمیم گرفتیم که منطقی تر است که جریان را نکشیم، بلکه به آن اطلاع دهیم که باید قطع شود. نحوه واکنش به این موضوع به خود جریان بستگی دارد. جزئیات بیشتر را می‌توانید در Oracle پیدا کنید " Whythread.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 ثانیه منتظر نخواهیم بود، بلکه بلافاصله «وقفه شده» را چاپ خواهیم کرد. این به این دلیل است که ما متد thread را فراخوانی کردیم 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تا زمانی که thread به صورت خارجی قطع شود اجرا می شود. نکته مهمی که باید در مورد پرچم isInterrupted بدانید این است که اگر آن را بگیریم InterruptedException، پرچم isInterruptedتنظیم مجدد می شود و سپس isInterruptedfalse برمی گردد. همچنین یک متد ثابت برای کلاس 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 نگاه کنید، وضعیت thread به این صورت خواهد بود: شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 4به لطف ابزارهای نظارتی، می توانید ببینید که چه اتفاقی در thread می افتد. این روش joinبسیار ساده است، زیرا به سادگی یک روش با کد جاوا است که waitدر حالی اجرا می شود که رشته ای که روی آن فراخوانی می شود زنده است. هنگامی که نخ از بین می رود (در پایان)، انتظار خاتمه می یابد. این تمام جادوی روش است join. بنابراین، بیایید به جالب ترین بخش برویم.

مانیتور مفهومی

در multithreading چیزی به نام Monitor وجود دارد. به طور کلی، کلمه مانیتور از لاتین به عنوان "نظارت" یا "نظارت" ترجمه شده است. در چارچوب این مقاله، ما سعی خواهیم کرد ماهیت را به خاطر بسپاریم، و برای کسانی که می خواهند، از شما می خواهم برای جزئیات بیشتر به مطالب از پیوندها بپردازید. بیایید سفر خود را با مشخصات زبان جاوا شروع کنیم، یعنی با JLS: " 17.1. Synchronization ". در زیر آمده است: شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 5معلوم می شود که برای همگام سازی بین موضوعات، جاوا از مکانیزم خاصی به نام "مانیتور" استفاده می کند. هر شی دارای یک نمایشگر مرتبط با آن است و رشته ها می توانند آن را قفل یا باز کنند. در مرحله بعد، ما یک آموزش آموزشی را در وب سایت Oracle خواهیم دید: " قفل های درونی و همگام سازی ". این آموزش توضیح می دهد که همگام سازی در جاوا حول یک موجودیت داخلی به نام قفل ذاتی یا قفل مانیتور ساخته شده است. اغلب چنین قفلی به سادگی "مانیتور" نامیده می شود. همچنین می بینیم که هر شی در جاوا دارای یک قفل ذاتی مرتبط با آن است. می توانید " جاوا - قفل های درونی و همگام سازی " را بخوانید. در مرحله بعد، مهم است که بفهمیم چگونه یک شی در جاوا می تواند با یک مانیتور مرتبط شود. هر شی در جاوا دارای یک هدر است - نوعی ابرداده داخلی که از طریق کد در دسترس برنامه نویس نیست، اما ماشین مجازی برای کار صحیح با اشیاء به آن نیاز دارد. هدر شی شامل یک MarkWord است که به شکل زیر است: شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 6

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

مقاله ای از Habr در اینجا بسیار مفید است: " اما چند رشته ای چگونه کار می کند؟ بخش اول: همگام سازی ." به این مقاله ارزش افزودن توضیحاتی از خلاصه بلوک وظیفه از باگ‌تکر JDK را دارد: " JDK-8183909 ". شما می توانید همین مطلب را در " JEP-8183909 " بخوانید. بنابراین، در جاوا، یک مانیتور با یک شی مرتبط است و رشته می تواند این رشته را مسدود کند، یا همچنین می گویند "قفل کن". ساده ترین مثال:
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 "مانیتور" نامیده می شود زیرا موضوع مسدود شده است و نمی تواند مانیتور را اشغال کند. همچنین می توانید وضعیت thread را در کد پیدا کنید، اما نام این حالت با اصطلاحات JVisualVM مطابقت ندارد، اگرچه آنها مشابه هستند. در این مورد، th1.getState()حلقه BLOCKEDfor برمی گردد ، زیرا در حالی که حلقه در حال اجرا است، مانیتور توسط نخ اشغال می شود و نخ مسدود می شود و نمی تواند تا زمانی که قفل بازگردد به کار خود ادامه دهد. علاوه بر بلوک های همگام سازی، کل روش را می توان همگام سازی کرد. به عنوان مثال، متدی از کلاس : lockmainth1HashTable
public synchronized int size() {
	return count;
}
در یک واحد زمان، این روش تنها توسط یک نخ اجرا خواهد شد. اما ما به یک قفل نیاز داریم، درست است؟ بله من به آن نیاز دارم. در مورد روش های شی، قفل خواهد بود this. بحث جالبی در مورد این موضوع وجود دارد: " آیا استفاده از روش همگام به جای بلوک همگام مزیتی دارد؟ ". اگر متد ثابت باشد، قفل نخواهد بود this(زیرا برای یک متد استاتیک نمی تواند باشد this)، بلکه شی کلاس خواهد بود (به عنوان مثال، Integer.class).

منتظر و منتظر روی مانیتور باشید. متدهای notify و notifyAll

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به . عجیب به نظر می رسد که روش های مرتبط با موضوع در . اما پاسخ در اینجا نهفته است. همانطور که به یاد داریم، هر شی در جاوا یک هدر دارد. هدر حاوی اطلاعات خدمات مختلفی است، از جمله اطلاعات مربوط به مانیتور - اطلاعات مربوط به وضعیت قفل. و همانطور که به یاد داریم، هر شی (یعنی هر نمونه) با یک موجودیت داخلی JVM به نام قفل ذاتی، که مانیتور نیز نامیده می شود، ارتباط دارد. در مثال بالا، وظیفه توضیح می‌دهد که بلوک همگام‌سازی را در مانیتور مرتبط با وارد می‌کنیم . اگر امکان گرفتن قفل روی این مانیتور وجود دارد، پس . رشته ای که این وظیفه را اجرا می کند، مانیتور را آزاد می کند ، اما به صف رشته هایی که منتظر اعلان در مانیتور هستند می پیوندد . این صف از رشته ها WAIT-SET نامیده می شود که به طور صحیح تری ماهیت را منعکس می کند. این بیشتر یک مجموعه است تا یک صف. موضوع یک موضوع جدید با وظیفه وظیفه ایجاد می کند، آن را شروع می کند و 3 ثانیه صبر می کند. این به احتمال زیاد به یک نخ جدید اجازه می دهد تا قفل را قبل از نخ گرفته و روی مانیتور در صف قرار بگیرد. پس از آن نخ خود وارد بلوک همگام سازی می شود و اعلان موضوع را روی مانیتور انجام می دهد. پس از ارسال اعلان، رشته مانیتور را رها می کند و رشته جدید (که قبلاً منتظر بود) پس از انتظار برای رها شدن مانیتور به اجرای خود ادامه می دهد. ارسال یک اعلان به یکباره فقط به یکی از رشته ها ( ) یا به همه رشته های موجود در صف امکان پذیر است ( ). می‌توانید در « تفاوت بین notify() و notifyAll() در جاوا » بیشتر بخوانید. توجه به این نکته مهم است که ترتیب اطلاع رسانی به پیاده سازی JVM بستگی دارد. می‌توانید در « چگونه گرسنگی را با notify و notifyall حل کنیم؟ » بیشتر بخوانید . همگام سازی را می توان بدون تعیین یک شی انجام داد. این می تواند زمانی انجام شود که نه یک بخش جداگانه از کد، بلکه یک روش کامل همگام سازی شده باشد. به عنوان مثال، برای متدهای استاتیک، قفل شی کلاس خواهد بود (که از طریق ): notifyjava.lang.ObjectObjectlockwaitlocklockmainmainmainlockmainlocklocknotifynotifyAll.class
public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
از نظر استفاده از قفل هر دو روش یکسان است. اگر روش ثابت نباشد، همگام سازی مطابق جریان انجام می شود instance، یعنی مطابق با this. ضمناً قبلاً گفتیم که با استفاده از روش getStateمی توانید وضعیت یک موضوع را بدست آورید. waitبنابراین در اینجا یک رشته است که توسط مانیتور در صف قرار می گیرد، اگر روش محدودیت زمانی برای انتظار تعیین کرده باشد، وضعیت WAITING یا TIMED_WAITING خواهد بود .شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 11

چرخه حیات یک نخ

همانطور که دیدیم، جریان در جریان زندگی وضعیت خود را تغییر می دهد. در اصل، این تغییرات چرخه زندگی نخ هستند. هنگامی که یک موضوع به تازگی ایجاد می شود، دارای وضعیت NEW است. در این موقعیت، هنوز شروع نشده است و جاوا Thread Scheduler هنوز چیزی در مورد موضوع جدید نمی داند. برای اینکه زمان‌بندی رشته در مورد یک موضوع بداند، باید با شماره تماس بگیرید thread.start(). سپس موضوع به حالت RUNNABLE می رود. بسیاری از طرح های نادرست در اینترنت وجود دارد که در آن حالت های Runnable و Running از هم جدا می شوند. اما این یک اشتباه است، زیرا ... جاوا بین وضعیت های "آماده برای اجرا" و "در حال اجرا" تفاوتی قائل نمی شود. وقتی رشته ای زنده است اما فعال نیست (قابل اجرا نیست)، در یکی از دو حالت است:
  • مسدود شده - منتظر ورود به بخش محافظت شده است، یعنی. به synchonizedبلوک
  • WAITING - بر اساس یک شرط منتظر رشته دیگری است. اگر شرط درست باشد، زمان‌بندی رشته موضوع را شروع می‌کند.
اگر رشته ای در انتظار زمان باشد، در وضعیت TIMED_WAITING است. اگر موضوع دیگر در حال اجرا نباشد (با موفقیت یا به استثنای کامل شده است)، به وضعیت TERMINATED می رود. برای پی بردن به وضعیت یک نخ (وضعیت آن)، از روش استفاده می شود getState. Thread ها نیز متدی دارند isAliveکه در صورت پایان نشدن رشته، مقدار true را برمی گرداند.

LockSupport و پارکینگ نخ

از جاوا 1.6 مکانیزم جالبی به نام LockSupport وجود داشت . شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 12این کلاس یک "مجوز" یا مجوز را با هر رشته ای که از آن استفاده می کند مرتبط می کند. فراخوانی متد parkدر صورت وجود مجوز فوراً برمی گردد و همان مجوز را در طول تماس اشغال می کند. در غیر این صورت مسدود است. فراخوانی روش unparkباعث می شود مجوز در صورتی که قبلاً در دسترس نباشد در دسترس باشد. تنها 1 مجوز وجود دارد. در 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 نیست، بلکه پارک است. شما نمی توانید جاوا را با یک موضوع خراب کنید: قسمت دوم - همگام سازی - 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و parkاز تمایز قائل می شود LockSupport. چرا این یکی اینقدر مهم است LockSupport؟ بیایید دوباره به API جاوا برگردیم و وضعیت موضوع انتظار را بررسی کنیم . همانطور که می بینید، تنها سه راه برای ورود به آن وجود دارد. 2 راه - این waitو join. و سومی این است LockSupport. قفل ها در جاوا بر اساس همان اصول ساخته شده اند 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 نگاه کنیم، می بینیم که رشته جدید تا زمانی که mainthread به آن قفل ندهد، پارک می شود. در اینجا می توانید اطلاعات بیشتری در مورد قفل ها بخوانید: " برنامه نویسی چند رشته ای در جاوا 8. قسمت دوم. همگام سازی دسترسی به اشیاء قابل تغییر " و " Java Lock API. نظریه و مثال استفاده ." برای درک بهتر پیاده سازی قفل ها، خواندن در مورد Phazer در نمای کلی " کلاس Phaser " مفید است. و در مورد همگام‌کننده‌های مختلف صحبت می‌کنید، باید مقاله Habré " Java.util.concurrent.* Synchronizers Reference " را بخوانید.

جمع

در این بررسی، روش‌های اصلی تعامل رشته‌ها در جاوا را بررسی کردیم. مواد اضافی: #ویاچسلاو
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION