تعارف
لہذا، ہم جانتے ہیں کہ جاوا میں تھریڈز ہیں، جن کے بارے میں آپ جائزہ میں پڑھ سکتے ہیں " آپ جاوا کو تھریڈ کے ساتھ خراب نہیں کر سکتے: حصہ اول - تھریڈز "۔ بیک وقت کام کرنے کے لیے تھریڈز کی ضرورت ہوتی ہے۔ لہذا، یہ بہت ممکن ہے کہ دھاگے کسی نہ کسی طرح ایک دوسرے کے ساتھ بات چیت کریں گے. آئیے معلوم کریں کہ یہ کیسے ہوتا ہے اور ہمارے پاس کون سے بنیادی کنٹرول ہیں۔پیداوار
Thread.yield() طریقہ پراسرار ہے اور شاذ و نادر ہی استعمال ہوتا ہے۔ انٹرنیٹ پر اس کی تفصیل کے بہت سے تغیرات ہیں۔ یہاں تک کہ کچھ لوگ دھاگوں کی کسی نہ کسی قطار کے بارے میں لکھتے ہیں، جس میں دھاگہ اپنی ترجیحات کو مدنظر رکھتے ہوئے نیچے چلا جائے گا۔ کوئی لکھتا ہے کہ تھریڈ اپنی حیثیت کو چلانے سے چلنے کے قابل میں بدل دے گا (حالانکہ ان سٹیٹس میں کوئی تقسیم نہیں ہے، اور جاوا ان میں فرق نہیں کرتا ہے)۔ لیکن حقیقت میں، سب کچھ بہت زیادہ نامعلوم اور، ایک معنی میں، آسان ہے. طریقہ دستاویزات کے عنوان پر،yield
ایک بگ ہے " JDK-6416721: (spec thread) Fix Thread.yield() javadoc "۔ اگر آپ اسے پڑھتے ہیں، تو یہ واضح ہے کہ درحقیقت یہ طریقہ yield
جاوا تھریڈ شیڈیولر کو صرف کچھ سفارشات پیش کرتا ہے کہ اس تھریڈ کو کم عملدرآمد کا وقت دیا جا سکتا ہے۔ لیکن اصل میں کیا ہوگا، آیا شیڈولر سفارش کو سنے گا اور عام طور پر کیا کرے گا اس کا انحصار JVM اور آپریٹنگ سسٹم کے نفاذ پر ہے۔ یا شاید کچھ دوسرے عوامل سے۔ تمام الجھنیں جاوا زبان کی ترقی کے دوران ملٹی تھریڈنگ پر دوبارہ غور کرنے کی وجہ سے ہوئی تھیں۔ آپ جائزہ میں مزید پڑھ سکتے ہیں " Java Thread.yield () کا مختصر تعارف "۔
نیند - سوتے ہوئے دھاگہ
ایک دھاگہ اس کے نفاذ کے دوران سو سکتا ہے۔ یہ دوسرے دھاگوں کے ساتھ تعامل کی سب سے آسان قسم ہے۔ وہ آپریٹنگ سسٹم جس پر جاوا ورچوئل مشین انسٹال ہوتی ہے، جہاں جاوا کوڈ ایکسیکیوٹ ہوتا ہے، اس کا اپنا تھریڈ شیڈیولر ہوتا ہے، جسے تھریڈ شیڈیولر کہتے ہیں۔ وہی فیصلہ کرتا ہے کہ کون سا تھریڈ کب چلانا ہے۔ پروگرامر جاوا کوڈ سے براہ راست اس شیڈیولر کے ساتھ بات چیت نہیں کر سکتا، لیکن وہ JVM کے ذریعے شیڈیولر سے تھریڈ کو کچھ دیر کے لیے روکنے کے لیے کہہ سکتا ہے، "اسے سونے کے لیے"۔ آپ مزید مضامین " Thread.sleep() " اور " How Multithreading Works " میں پڑھ سکتے ہیں۔ مزید یہ کہ، آپ یہ جان سکتے ہیں کہ ونڈوز OS میں تھریڈز کیسے کام کرتے ہیں: " 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Процесса
: جیسا کہ آپ دیکھ سکتے ہیں، ہمارا تھریڈ سلیپنگ سٹیٹس میں داخل ہو گیا ہے۔ درحقیقت، موجودہ دھاگے کو سونا زیادہ خوبصورتی سے کیا جا سکتا ہے:
try {
TimeUnit.SECONDS.sleep(60);
System.out.println("Waked up");
} catch (InterruptedException e) {
e.printStackTrace();
}
آپ نے شاید محسوس کیا ہے کہ ہم ہر جگہ کارروائی کرتے ہیں InterruptedException
؟ آئیے سمجھتے ہیں کیوں۔
تھریڈ یا Thread.interrupt میں خلل ڈالنا
بات یہ ہے کہ جب دھاگہ نیند میں انتظار کر رہا ہے، کوئی اس انتظار میں رکاوٹ ڈالنا چاہے گا۔ اس صورت میں، ہم اس طرح کی ایک استثنا کو ہینڈل کرتے ہیں. یہ طریقہ کوThread.stop
فرسودہ قرار دینے کے بعد کیا گیا، یعنی پرانا اور استعمال کے لیے ناپسندیدہ۔ اس کی وجہ یہ تھی کہ جب طریقہ کو بلایا گیا تھا، تو stop
تھریڈ کو صرف "قتل" کیا گیا تھا، جو کہ بہت غیر متوقع تھا۔ ہم نہیں جان سکے کہ بہاؤ کب روکا جائے گا، ہم ڈیٹا کی مستقل مزاجی کی ضمانت نہیں دے سکے۔ تصور کریں کہ آپ کسی فائل میں ڈیٹا لکھ رہے ہیں اور پھر ندی تباہ ہو گئی ہے۔ لہذا، انہوں نے فیصلہ کیا کہ یہ زیادہ منطقی ہوگا کہ بہاؤ کو ختم نہ کیا جائے، بلکہ اسے مطلع کیا جائے کہ اس میں خلل ڈالا جائے۔ اس پر کس طرح کا رد عمل ظاہر کرنا خود بہاؤ پر منحصر ہے۔ مزید تفصیلات اوریکل کے " Why is 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 سیکنڈ انتظار نہیں کریں گے، لیکن فوری طور پر 'Interrupted' پرنٹ کریں گے۔ اس کی وجہ یہ ہے کہ ہم نے تھریڈ کا طریقہ کہا ہے 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 flag کے بارے میں جاننے کے لیے اہم بات یہ ہے کہ اگر ہم اسے پکڑتے ہیں تو InterruptedException
جھنڈا isInterrupted
دوبارہ ترتیب دیا جاتا ہے، اور پھر isInterrupted
یہ غلط واپس آجائے گا۔ Thread کلاس کے لیے ایک جامد طریقہ بھی ہے جو صرف موجودہ تھریڈ پر لاگو ہوتا ہے - Thread.interrupted() ، لیکن یہ طریقہ جھنڈے کو غلط پر سیٹ کرتا ہے! آپ باب میں مزید پڑھ سکتے ہیں " دھاگے میں رکاوٹ "۔
شامل ہوں — دوسرے تھریڈ کے مکمل ہونے کا انتظار کر رہے ہیں۔
انتظار کی سادہ ترین قسم دوسرے تھریڈ کے مکمل ہونے کا انتظار کر رہی ہے۔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 کے ذریعے دیکھیں تو تھریڈ کی حالت اس طرح نظر آئے گی: مانیٹرنگ ٹولز کا شکریہ، آپ دیکھ سکتے ہیں کہ تھریڈ کے ساتھ کیا ہو رہا ہے۔ طریقہ join
کافی آسان ہے، کیونکہ یہ صرف جاوا کوڈ کے ساتھ ایک طریقہ ہے جو اس پر عمل کرتا ہے wait
جب کہ جس تھریڈ پر اسے کہا جاتا ہے وہ زندہ ہے۔ ایک بار جب دھاگہ مر جاتا ہے (ختم ہونے پر)، انتظار ختم ہوجاتا ہے۔ یہ طریقہ کا سارا جادو ہے join
۔ لہذا، سب سے زیادہ دلچسپ حصہ پر چلتے ہیں.
تصور مانیٹر
ملٹی تھریڈنگ میں مانیٹر جیسی چیز ہوتی ہے۔ عام طور پر، لفظ مانیٹر کا ترجمہ لاطینی سے "اوورسیر" یا "اوورسیر" کے طور پر کیا جاتا ہے۔ اس مضمون کے فریم ورک کے اندر، ہم جوہر کو یاد رکھنے کی کوشش کریں گے، اور ان لوگوں کے لیے جو چاہتے ہیں، میں آپ سے کہتا ہوں کہ تفصیلات کے لیے لنکس سے مواد میں غوطہ لگائیں۔ آئیے اپنے سفر کا آغاز جاوا زبان کی تفصیلات کے ساتھ کرتے ہیں، یعنی JLS کے ساتھ: " 17.1. ہم آہنگی "۔ یہ مندرجہ ذیل کہتا ہے: یہ پتہ چلتا ہے کہ دھاگوں کے درمیان ہم آہنگی کے مقصد کے لیے، جاوا ایک مخصوص طریقہ کار استعمال کرتا ہے جسے "مانیٹر" کہتے ہیں۔ ہر آبجیکٹ کے ساتھ ایک مانیٹر منسلک ہوتا ہے، اور تھریڈز اسے لاک یا انلاک کر سکتے ہیں۔ اگلا، ہمیں اوریکل ویب سائٹ پر ایک تربیتی ٹیوٹوریل ملے گا: " انٹرنسک لاک اینڈ سنکرونائزیشن "۔ یہ ٹیوٹوریل بتاتا ہے کہ جاوا میں ہم آہنگی ایک اندرونی وجود کے گرد بنائی گئی ہے جسے اندرونی تالا یا مانیٹر لاک کہا جاتا ہے۔ اکثر اس طرح کے تالا کو صرف "مانیٹر" کہا جاتا ہے۔ ہم دوبارہ یہ بھی دیکھتے ہیں کہ جاوا میں ہر شے کے ساتھ ایک اندرونی تالا جڑا ہوا ہے۔ آپ " جاوا - اندرونی تالے اور مطابقت پذیری " پڑھ سکتے ہیں۔ اگلا، یہ سمجھنا ضروری ہے کہ جاوا میں کسی چیز کو مانیٹر کے ساتھ کیسے جوڑا جا سکتا ہے۔ جاوا میں ہر آبجیکٹ کا ایک ہیڈر ہوتا ہے - ایک قسم کا اندرونی میٹا ڈیٹا جو کوڈ سے پروگرامر کے لیے دستیاب نہیں ہوتا ہے، لیکن ورچوئل مشین کو اشیاء کے ساتھ صحیح طریقے سے کام کرنے کی ضرورت ہوتی ہے۔ آبجیکٹ ہیڈر میں ایک MarkWord شامل ہے جو اس طرح لگتا ہے:https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf
public class HelloWorld{
public static void main(String []args){
Object object = new Object();
synchronized(object) {
System.out.println("Hello World");
}
}
}
لہذا، کلیدی لفظ کا استعمال کرتے ہوئے، synchronized
موجودہ تھریڈ (جس میں کوڈ کی یہ لائنیں عمل میں آتی ہیں) آبجیکٹ سے وابستہ مانیٹر کو استعمال کرنے کی کوشش کرتا ہے object
اور "لاک حاصل کریں" یا "مانیٹر پر قبضہ کریں" (دوسرا آپشن بھی افضل ہے)۔ اگر مانیٹر کے لیے کوئی تنازعہ نہیں ہے (یعنی کوئی اور ایک ہی چیز پر ہم آہنگی نہیں کرنا چاہتا ہے)، جاوا ایک اصلاح کرنے کی کوشش کر سکتا ہے جسے "متعصب لاکنگ" کہا جاتا ہے۔ مارک ورڈ میں آبجیکٹ کے عنوان میں متعلقہ ٹیگ اور اس کا ریکارڈ ہوگا کہ مانیٹر کس تھریڈ سے منسلک ہے۔ یہ مانیٹر پر قبضہ کرتے وقت اوور ہیڈ کو کم کرتا ہے۔ اگر مانیٹر پہلے ہی کسی دوسرے دھاگے سے بندھا ہوا ہے، تو یہ لاکنگ کافی نہیں ہے۔ JVM اگلی لاکنگ قسم - بنیادی لاکنگ پر سوئچ کرتا ہے۔ یہ موازنہ اور تبادلہ (CAS) آپریشنز کا استعمال کرتا ہے۔ ایک ہی وقت میں، مارک ورڈ میں ہیڈر اب خود مارک ورڈ کو ذخیرہ نہیں کرتا ہے، لیکن اس کے سٹوریج کا ایک لنک + ٹیگ کو تبدیل کیا جاتا ہے تاکہ JVM سمجھے کہ ہم بنیادی لاکنگ استعمال کر رہے ہیں۔ اگر کئی دھاگوں کے مانیٹر کے لیے تنازعہ ہے (ایک نے مانیٹر کو پکڑ لیا ہے، اور دوسرا مانیٹر کے جاری ہونے کا انتظار کر رہا ہے)، تو مارک ورڈ میں ٹیگ تبدیل ہو جاتا ہے، اور مارک ورڈ مانیٹر کے لیے ایک حوالہ محفوظ کرنا شروع کر دیتا ہے۔ ایک آبجیکٹ - JVM کا کچھ اندرونی وجود۔ جیسا کہ JEP میں بتایا گیا ہے، اس صورت میں، اس ہستی کو ذخیرہ کرنے کے لیے Native Heap میموری کے علاقے میں جگہ درکار ہے۔ اس داخلی ہستی کے اسٹوریج لوکیشن کا لنک مارک ورڈ آبجیکٹ میں موجود ہوگا۔ اس طرح، جیسا کہ ہم دیکھتے ہیں، مانیٹر واقعی ایک ایسا طریقہ کار ہے جو مشترکہ وسائل تک متعدد تھریڈز کی رسائی کو یقینی بناتا ہے۔ اس میکانزم کے کئی نفاذ ہیں جن کے درمیان JVM سوئچ کرتا ہے۔ لہذا، سادگی کے لئے، ایک مانیٹر کے بارے میں بات کرتے وقت، ہم اصل میں تالے کے بارے میں بات کر رہے ہیں.
ہم وقت ساز اور تالے کے ذریعے انتظار کر رہے ہیں۔
مانیٹر کا تصور، جیسا کہ ہم نے پہلے دیکھا تھا، ایک "ہم وقت سازی بلاک" (یا، جیسا کہ اسے ایک اہم سیکشن بھی کہا جاتا ہے) کے تصور سے گہرا تعلق ہے۔ آئیے ایک مثال دیکھتے ہیں: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 میں یہ اس طرح نظر آئے گا: جیسا کہ آپ دیکھ سکتے ہیں، JVisualVM میں اسٹیٹس کو "مانیٹر" کہا جاتا ہے کیونکہ تھریڈ بلاک ہے اور مانیٹر پر قبضہ نہیں کر سکتا۔ آپ کوڈ میں تھریڈ کی حالت بھی جان سکتے ہیں، لیکن اس ریاست کا نام JVisualVM کی شرائط سے میل نہیں کھاتا، حالانکہ وہ ایک جیسے ہیں۔ اس صورت میں، th1.getState()
لوپ for
واپس آجائے گا BLOCKED ، کیونکہ جب لوپ چل رہا ہوتا ہے، مانیٹر پر دھاگے کا lock
قبضہ ہوتا ہے ، اور دھاگے کو بلاک کر دیا جاتا ہے اور جب تک تالا واپس نہیں آ جاتا کام جاری نہیں رکھ سکتا۔ ہم وقت سازی کے بلاکس کے علاوہ، ایک پورا طریقہ ہم آہنگ کیا جا سکتا ہے۔ مثال کے طور پر، کلاس سے ایک طریقہ : main
th1
HashTable
public synchronized int size() {
return count;
}
وقت کی ایک اکائی میں، یہ طریقہ صرف ایک دھاگے کے ذریعے عمل میں لایا جائے گا۔ لیکن ہمیں ایک تالے کی ضرورت ہے، ٹھیک ہے؟ ہاں مجھے اس کی ضرورت ہے۔ آبجیکٹ کے طریقوں کے معاملے میں، تالا ہو جائے گا this
. اس موضوع پر ایک دلچسپ بحث ہے: " کیا سنکرونائزڈ بلاک کے بجائے مطابقت پذیر طریقہ استعمال کرنے کا کوئی فائدہ ہے؟ "۔ اگر طریقہ جامد ہے، تو تالا نہیں ہوگا this
(چونکہ جامد طریقہ کے لیے نہیں ہو سکتا ہے this
)، لیکن کلاس آبجیکٹ (مثال کے طور پر، Integer.class
)۔
مانیٹر پر انتظار کریں اور انتظار کریں۔ مطلع کریں اور تمام طریقوں کو مطلع کریں۔
تھریڈ میں انتظار کا ایک اور طریقہ ہے، جو مانیٹر سے منسلک ہے۔ برعکس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 میں یہ اس طرح نظر آئے گا: یہ سمجھنے کے لیے کہ یہ کیسے کام کرتا ہے، آپ کو یاد رکھنا چاہیے کہ طریقوں کا wait
حوالہ دیتے ہیں ۔ یہ عجیب لگتا ہے کہ دھاگے سے متعلق طریقے . لیکن یہاں جواب مضمر ہے۔ جیسا کہ ہمیں یاد ہے، جاوا میں ہر شے کا ایک ہیڈر ہوتا ہے۔ ہیڈر میں مختلف سروس کی معلومات شامل ہیں، بشمول مانیٹر کے بارے میں معلومات — لاک کرنے کی حالت کے بارے میں ڈیٹا۔ اور جیسا کہ ہمیں یاد ہے، ہر آبجیکٹ (یعنی ہر مثال) کا ایک اندرونی JVM ہستی کے ساتھ تعلق ہے جسے اندرونی تالا کہا جاتا ہے، جسے مانیٹر بھی کہا جاتا ہے۔ اوپر کی مثال میں، ٹاسک بیان کرتا ہے کہ ہم اس سے منسلک مانیٹر پر سنکرونائزیشن بلاک داخل کرتے ہیں ۔ اگر اس مانیٹر پر تالا حاصل کرنا ممکن ہے، تو . اس کام کو انجام دینے والا دھاگہ مانیٹر کو جاری کرے گا ، لیکن مانیٹر پر اطلاع کے منتظر دھاگوں کی قطار میں شامل ہو جائے گا ۔ دھاگوں کی اس قطار کو WAIT-SET کہا جاتا ہے، جو جوہر کو زیادہ درست طریقے سے ظاہر کرتا ہے۔ یہ قطار سے زیادہ سیٹ ہے۔ تھریڈ ٹاسک ٹاسک کے ساتھ ایک نیا تھریڈ بناتا ہے، اسے شروع کرتا ہے اور 3 سیکنڈ تک انتظار کرتا ہے۔ یہ اعلیٰ امکان کے ساتھ، دھاگے سے پہلے تالے کو پکڑنے اور مانیٹر پر قطار لگانے کے لیے ایک نئے دھاگے کی اجازت دیتا ہے۔ جس کے بعد تھریڈ خود ہی سنکرونائزیشن بلاک میں داخل ہوتا ہے اور مانیٹر پر تھریڈ کی اطلاع دیتا ہے۔ نوٹیفکیشن بھیجے جانے کے بعد، تھریڈ مانیٹر کو جاری کرتا ہے ، اور نیا دھاگہ (جو پہلے انتظار کر رہا تھا) مانیٹر کے جاری ہونے کا انتظار کرنے کے بعد جاری رہتا ہے۔ دھاگوں میں سے صرف ایک ( ) یا قطار میں موجود تمام دھاگوں کو ایک ساتھ اطلاع بھیجنا ممکن ہے ( )۔ آپ جاوا میں "notify() اور notifyAll() کے درمیان فرق " میں مزید پڑھ سکتے ہیں۔ یہ نوٹ کرنا ضروری ہے کہ نوٹیفکیشن آرڈر JVM کے نفاذ پر منحصر ہے۔ آپ " notify اور notifyall کے ساتھ بھوک کو کیسے حل کریں؟ " میں مزید پڑھ سکتے ہیں۔ ہم وقت سازی کسی چیز کی وضاحت کیے بغیر کی جا سکتی ہے۔ یہ اس وقت کیا جا سکتا ہے جب کوڈ کا الگ سیکشن مطابقت پذیر نہ ہو بلکہ ایک پورا طریقہ ہو۔ مثال کے طور پر، جامد طریقوں کے لیے لاک کلاس آبجیکٹ ہوگا (بذریعہ حاصل کیا گیا ): notify
java.lang.Object
Object
lock
wait
lock
lock
main
main
main
lock
main
lock
lock
notify
notifyAll
.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 ہوگی ۔
ایک دھاگے کا لائف سائیکل
جیسا کہ ہم نے دیکھا، بہاؤ زندگی کے دوران اپنی حیثیت بدلتا ہے۔ جوہر میں، یہ تبدیلیاں دھاگے کا لائف سائیکل ہیں۔ جب کوئی تھریڈ ابھی بنایا جاتا ہے تو اس کی نئی حیثیت ہوتی ہے۔ اس پوزیشن میں، یہ ابھی تک شروع نہیں ہوا ہے اور جاوا تھریڈ شیڈیولر کو ابھی تک نئے تھریڈ کے بارے میں کچھ معلوم نہیں ہے۔ تھریڈ شیڈیولر کو تھریڈ کے بارے میں جاننے کے لیے، آپ کو کال کرنا چاہیےthread.start()
۔ پھر تھریڈ RUNNABLE حالت میں چلا جائے گا۔ انٹرنیٹ پر بہت سی غلط اسکیمیں ہیں جہاں رن ایبل اور رننگ اسٹیٹس کو الگ کیا گیا ہے۔ لیکن یہ ایک غلطی ہے، کیونکہ... جاوا "دوڑنے کے لئے تیار" اور "چلنے والے" کی حالتوں میں فرق نہیں کرتا ہے۔ جب کوئی دھاگہ زندہ ہے لیکن فعال نہیں ہے (چلانے کے قابل نہیں ہے)، تو یہ دو حالتوں میں سے ایک میں ہے:
- بلاک - ایک محفوظ حصے میں داخلے کا انتظار کر رہا ہے، یعنی
synchonized
بلاک کو . - انتظار - ایک شرط کی بنیاد پر دوسرے تھریڈ کا انتظار کر رہا ہے۔ اگر شرط درست ہے تو، تھریڈ شیڈولر تھریڈ شروع کرتا ہے۔
getState
۔ تھریڈز کا ایک طریقہ بھی ہوتا ہے isAlive
جو درست ہو جاتا ہے اگر تھریڈ کو ختم نہیں کیا جاتا ہے۔
لاک سپورٹ اور تھریڈ پارکنگ
جاوا 1.6 کے بعد سے ایک دلچسپ طریقہ کار تھا جسے LockSupport کہتے ہیں ۔ یہ کلاس ہر اس تھریڈ کے ساتھ "اجازت" یا اجازت جوڑتی ہے جو اسے استعمال کرتا ہے۔ اگر اجازت نامہ دستیاب ہو تو میتھڈ کال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 میں دیکھیں تو ہم دیکھیں گے کہ ہماری ریاست انتظار نہیں بلکہ پارک ہے۔ آئیے ایک اور مثال دیکھتے ہیں:
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
from synchronized
اور park
from کے درمیان فرق کرتا ہے LockSupport
۔ یہ ایک اتنا اہم کیوں ہے LockSupport
؟ آئیے جاوا API کی طرف دوبارہ رجوع کریں اور Thread State WAITING کو دیکھیں ۔ جیسا کہ آپ دیکھ سکتے ہیں، اس میں داخل ہونے کے صرف تین طریقے ہیں۔ 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 میں دیکھیں تو ہم دیکھیں گے کہ نیا تھریڈ اس وقت تک کھڑا رہے گا جب تک کہ main
تھریڈ اسے لاک نہیں دیتا۔ آپ یہاں تالے کے بارے میں مزید پڑھ سکتے ہیں: " جاوا 8 میں ملٹی تھریڈ پروگرامنگ۔ حصہ دو۔ متغیر اشیاء تک رسائی کو ہم وقت سازی کرنا " اور " Java Lock API۔ تھیوری اور استعمال کی مثال ۔" تالے کے نفاذ کو بہتر طور پر سمجھنے کے لیے، Phazer کے بارے میں جائزہ " Phaser Class " میں پڑھنا مفید ہے۔ اور مختلف سنکرونائزرز کے بارے میں بات کرتے ہوئے، آپ کو Habré " Java.util.concurrent.* Synchronizers Reference " پر مضمون ضرور پڑھنا چاہیے ۔
کل
اس جائزے میں، ہم نے جاوا میں تھریڈز کے تعامل کے اہم طریقوں کو دیکھا۔ اضافی مواد:- مانیٹرز - جاوا سنکرونائزیشن کا بنیادی خیال
- سنکرونائزر حوالہ java.util.concurrent۔*
- ایک انٹرویو میں ملٹی تھریڈنگ کے بارے میں سوالات کے جوابات
GO TO FULL VERSION