CodeGym /جاوا بلاگ /Random-UR /ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ III - تعامل
John Squirrels
سطح
San Francisco

ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ III - تعامل

گروپ میں شائع ہوا۔
دھاگوں کے تعامل کے طریقے کی تفصیلات کا ایک مختصر جائزہ۔ پہلے، ہم نے دیکھا کہ تھریڈز ایک دوسرے کے ساتھ کیسے ہم آہنگ ہوتے ہیں۔ اس بار ہم ان مسائل کا جائزہ لیں گے جو دھاگوں کے باہمی تعامل سے پیدا ہو سکتے ہیں، اور ہم ان سے بچنے کے طریقے کے بارے میں بات کریں گے۔ مزید گہرائی سے مطالعہ کے لیے ہم کچھ مفید لنکس بھی فراہم کریں گے۔ ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 1

تعارف

تو، ہم جانتے ہیں کہ جاوا میں تھریڈز ہیں۔ آپ اس کے بارے میں بیٹر اکٹھے: جاوا اور تھریڈ کلاس کے عنوان سے جائزے میں پڑھ سکتے ہیں ۔ حصہ I - عمل درآمد کے سلسلے ۔ اور ہم نے اس حقیقت کی کھوج کی کہ تھریڈز ایک دوسرے کے ساتھ بہتر ایک ساتھ: جاوا اور تھریڈ کلاس کے عنوان سے ہم آہنگ ہوسکتے ہیں۔ حصہ II - ہم آہنگی . یہ اس بارے میں بات کرنے کا وقت ہے کہ دھاگے ایک دوسرے کے ساتھ کیسے تعامل کرتے ہیں۔ وہ مشترکہ وسائل کا اشتراک کیسے کرتے ہیں؟ یہاں کیا مسائل پیدا ہو سکتے ہیں؟ ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 2

تعطل

سب سے خوفناک مسئلہ تعطل ہے۔ تعطل اس وقت ہوتا ہے جب دو یا زیادہ دھاگے ہمیشہ دوسرے کا انتظار کر رہے ہوتے ہیں۔ ہم اوریکل ویب پیج سے ایک مثال لیں گے جو تعطل کو بیان کرتا ہے :
public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s bowed to me!%n",
                    this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s bowed back to me!%n",
                    this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        new Thread(() -> alphonse.bow(gaston)).start();
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}
ڈیڈ لاک یہاں پہلی بار نہیں ہو سکتا، لیکن اگر آپ کا پروگرام ہینگ ہو جاتا ہے، تو یہ چلانے کا وقت ہے jvisualvm: ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 3JVisualVM پلگ ان انسٹال ہونے کے ساتھ (Tools -> Plugins کے ذریعے)، ہم دیکھ سکتے ہیں کہ تعطل کہاں واقع ہوا:
"Thread-1" - Thread t@12
   java.lang.Thread.State: BLOCKED
	at Deadlock$Friend.bowBack(Deadlock.java:16)
	- waiting to lock <33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
تھریڈ 1 تھریڈ 0 سے لاک کا انتظار کر رہا ہے۔ ایسا کیوں ہوتا ہے؟ Thread-1چلنا شروع ہوتا ہے اور Friend#bowطریقہ کار کو انجام دیتا ہے۔ اسے کلیدی لفظ کے ساتھ نشان زد کیا گیا ہے ، جس کا مطلب ہے کہ ہم (موجودہ آبجیکٹ) synchronizedکے لیے مانیٹر حاصل کر رہے ہیں ۔ thisطریقہ کا ان پٹ دوسرے Friendآبجیکٹ کا حوالہ تھا۔ اب، Thread-1دوسرے طریقے پر عمل کرنا چاہتا ہے Friend، اور ایسا کرنے کے لیے اس کا لاک حاصل کرنا ہوگا۔ لیکن اگر دوسرا دھاگہ (اس معاملے میں Thread-0) طریقہ میں داخل ہونے میں کامیاب ہو گیا bow()، تو لاک پہلے ہی حاصل کر لیا گیا ہے اور Thread-1انتظار کر رہا ہے Thread-0، اور اس کے برعکس۔ یہ تعطل ناقابل حل ہے، اور ہم اسے تعطل کہتے ہیں۔ موت کی گرفت کی طرح جسے جاری نہیں کیا جا سکتا، تعطل ایک باہمی رکاوٹ ہے جسے توڑا نہیں جا سکتا۔ تعطل کی ایک اور وضاحت کے لیے، آپ یہ ویڈیو دیکھ سکتے ہیں: ڈیڈ لاک اور لائیو لاک کی وضاحت ۔

لائیو لاک

اگر تعطل ہے تو کیا لائیو لاک بھی ہے؟ ہاں، وہاں ہے :) لائیو لاک اس وقت ہوتا ہے جب دھاگے ظاہری طور پر زندہ نظر آتے ہیں، لیکن وہ کچھ کرنے سے قاصر ہوتے ہیں، کیونکہ ان کے لیے اپنا کام جاری رکھنے کے لیے جو شرطیں درکار ہوتی ہیں وہ پوری نہیں ہوتیں۔ بنیادی طور پر، لائیو لاک ڈیڈ لاک کی طرح ہے، لیکن دھاگے مانیٹر کے انتظار میں "ہنگ" نہیں ہوتے ہیں۔ اس کے بجائے، وہ ہمیشہ کے لیے کچھ کر رہے ہیں۔ مثال کے طور پر:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class App {
    public static final String ANSI_BLUE = "\u001B[34m";
    public static final String ANSI_PURPLE = "\u001B[35m";

    public static void log(String text) {
        String name = Thread.currentThread().getName(); // Like "Thread-1" or "Thread-0"
        String color = ANSI_BLUE;
        int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
        if (val != 0) {
            color = ANSI_PURPLE;
        }
        System.out.println(color + name + ": " + text + color);
        try {
            System.out.println(color + name + ": wait for " + val + " sec" + color);
            Thread.currentThread().sleep(val * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Lock first = new ReentrantLock();
        Lock second = new ReentrantLock();

        Runnable locker = () -> {
            boolean firstLocked = false;
            boolean secondLocked = false;
            try {
                while (!firstLocked || !secondLocked) {
                    firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
                    log("First Locked: " + firstLocked);
                    secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
                    log("Second Locked: " + secondLocked);
                }
                first.unlock();
                second.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        new Thread(locker).start();
        new Thread(locker).start();
    }
}
اس کوڈ کی کامیابی کا انحصار اس ترتیب پر ہے جس میں جاوا تھریڈ شیڈیولر تھریڈز کو شروع کرتا ہے۔ اگر Thead-1پہلے شروع ہوتا ہے، تو ہمیں لائیو لاک ملتا ہے:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
جیسا کہ آپ مثال سے دیکھ سکتے ہیں، دونوں تھریڈز باری باری دونوں تالے حاصل کرنے کی کوشش کرتے ہیں، لیکن وہ ناکام ہو جاتے ہیں۔ لیکن، وہ تعطل کا شکار نہیں ہیں۔ ظاہری طور پر سب کچھ ٹھیک ہے اور وہ اپنا کام کر رہے ہیں۔ ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 4JVisualVM کے مطابق، ہم نیند کے ادوار اور پارک کی مدت دیکھتے ہیں (یہ اس وقت ہوتا ہے جب ایک دھاگہ تالا حاصل کرنے کی کوشش کرتا ہے — یہ پارک کی حالت میں داخل ہوتا ہے، جیسا کہ ہم نے پہلے بات کی تھی جب ہم نے تھریڈ سنکرونائزیشن کے بارے میں بات کی تھی ) ۔ آپ لائیو لاک کی ایک مثال یہاں دیکھ سکتے ہیں: Java - Thread Livelock ۔

فاقہ کشی

تعطل اور لائیو لاک کے علاوہ، ایک اور مسئلہ ہے جو ملٹی تھریڈنگ کے دوران ہو سکتا ہے: بھوک۔ یہ رجحان بلاک کرنے کی پچھلی شکلوں سے مختلف ہے جس میں تھریڈز کو بلاک نہیں کیا گیا ہے - ان کے پاس کافی وسائل نہیں ہیں۔ نتیجے کے طور پر، جب کہ کچھ تھریڈز پر عمل درآمد کا سارا وقت لگتا ہے، دوسرے چلانے سے قاصر ہیں: ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 5

https://www.logicbig.com/

آپ یہاں ایک بہترین مثال دیکھ سکتے ہیں: Java - Thread Starvation and Fairness ۔ Thread.sleep()یہ مثال دکھاتی ہے کہ فاقہ کشی کے دوران تھریڈز کے ساتھ کیا ہوتا ہے اور کس طرح سے ایک چھوٹی تبدیلی Thread.wait()آپ کو بوجھ کو یکساں طور پر تقسیم کرنے دیتی ہے۔ ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 6

ریس کے حالات

ملٹی تھریڈنگ میں، "نسل کی حالت" جیسی چیز ہوتی ہے۔ یہ رجحان اس وقت ہوتا ہے جب تھریڈز کسی وسائل کا اشتراک کرتے ہیں، لیکن کوڈ اس طرح لکھا جاتا ہے کہ یہ صحیح اشتراک کو یقینی نہیں بناتا ہے۔ ایک مثال پر نظر ڈالیں:
public class App {
    public static int value = 0;

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                int oldValue = value;
                int newValue = ++value;
                if (oldValue + 1 != newValue) {
                    throw new IllegalStateException(oldValue + " + 1 = " + newValue);
                }
            }
        };
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}
یہ کوڈ پہلی بار خرابی پیدا نہیں کر سکتا۔ جب ایسا ہوتا ہے، تو یہ اس طرح نظر آسکتا ہے:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
	at App.lambda$main$0(App.java:13)
	at java.lang.Thread.run(Thread.java:745)
جیسا کہ آپ دیکھ سکتے ہیں، newValueقدر تفویض کرتے وقت کچھ غلط ہو گیا۔ newValueبہت بڑا ہے. valueریس کی حالت کی وجہ سے، تھریڈز میں سے ایک دو بیانات کے درمیان متغیر کو تبدیل کرنے میں کامیاب رہا ۔ معلوم ہوا کہ دھاگوں کے درمیان دوڑ لگی ہوئی ہے۔ اب سوچیں کہ مالیاتی لین دین کے ساتھ ایک جیسی غلطیاں نہ کرنا کتنا ضروری ہے... مثالیں اور خاکے یہاں بھی دیکھے جا سکتے ہیں: جاوا تھریڈ میں نسل کی حالت کی نقل کرنے کے لیے کوڈ ۔

غیر مستحکم

دھاگوں کے تعامل کے بارے میں بات کرتے ہوئے، volatileکلیدی لفظ قابل ذکر ہے۔ آئیے ایک سادہ مثال دیکھیں:
public class App {
    public static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Runnable whileFlagFalse = () -> {
            while(!flag) {
            }
            System.out.println("Flag is now TRUE");
        };

        new Thread(whileFlagFalse).start();
        Thread.sleep(1000);
        flag = true;
    }
}
سب سے دلچسپ بات یہ ہے کہ اس کے کام نہ کرنے کا بہت زیادہ امکان ہے۔ نئے تھریڈ کو فیلڈ میں تبدیلی نظر نہیں آئے گی flag۔ فیلڈ کے لیے اسے ٹھیک کرنے کے لیے flag، ہمیں volatileکلیدی لفظ استعمال کرنے کی ضرورت ہے۔ کیسے اور کیوں؟ پروسیسر تمام اعمال انجام دیتا ہے۔ لیکن حساب کے نتائج کو کہیں نہ کہیں محفوظ کیا جانا چاہیے۔ اس کے لیے مین میموری ہے اور پروسیسر کا کیش ہے۔ ایک پروسیسر کے کیشز میموری کے ایک چھوٹے سے ٹکڑے کی طرح ہوتے ہیں جو کہ مرکزی میموری تک رسائی کے مقابلے میں زیادہ تیزی سے ڈیٹا تک رسائی کے لیے استعمال ہوتے ہیں۔ لیکن ہر چیز کا ایک منفی پہلو ہوتا ہے: ہو سکتا ہے کہ کیشے میں موجود ڈیٹا اپ ٹو ڈیٹ نہ ہو (جیسا کہ اوپر کی مثال میں، جب فلیگ فیلڈ کی قدر کو اپ ڈیٹ نہیں کیا گیا تھا)۔ لہذا، volatileکلیدی لفظ JVM کو بتاتا ہے کہ ہم اپنے متغیر کو کیش نہیں کرنا چاہتے۔ یہ تمام تھریڈز پر تازہ ترین نتیجہ دیکھنے کی اجازت دیتا ہے۔ یہ ایک انتہائی آسان وضاحت ہے۔ جہاں تک volatileکلیدی لفظ کا تعلق ہے، میں انتہائی سفارش کرتا ہوں کہ آپ اس مضمون کو پڑھیں ۔ مزید معلومات کے لیے، میں آپ کو Java Memory Model اور Java Volatile Keyword پڑھنے کا مشورہ بھی دیتا ہوں ۔ مزید برآں، یہ یاد رکھنا ضروری ہے کہ یہ volatileمرئیت کے بارے میں ہے، نہ کہ تبدیلیوں کے جوہری کے بارے میں۔ "ریس کنڈیشنز" سیکشن میں موجود کوڈ کو دیکھتے ہوئے، ہمیں IntelliJ IDEA میں ایک ٹول ٹِپ نظر آئے گا: یہ معائنہ IntelliJ IDEA میں شمارہ IDEA-61117 ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 7 کے حصے کے طور پر شامل کیا گیا تھا ، جو 2010 میں ریلیز نوٹس میں درج تھا ۔

جوہری

ایٹمی کارروائیاں ایسی کارروائیاں ہیں جنہیں تقسیم نہیں کیا جا سکتا۔ مثال کے طور پر، متغیر کو قدر تفویض کرنے کا عمل جوہری ہونا چاہیے۔ بدقسمتی سے، انکریمنٹ آپریشن ایٹم نہیں ہے، کیونکہ انکریمنٹ کے لیے زیادہ سے زیادہ تین CPU آپریشنز کی ضرورت ہوتی ہے: پرانی ویلیو حاصل کریں، اس میں ایک کا اضافہ کریں، پھر ویلیو کو محفوظ کریں۔ جوہری اہمیت کیوں ہے؟ انکریمنٹ آپریشن کے ساتھ، اگر ریس کی شرط ہے، تو مشترکہ وسیلہ (یعنی مشترکہ قدر) کسی بھی وقت اچانک تبدیل ہو سکتا ہے۔ مزید برآں، 64 بٹ ڈھانچے پر مشتمل آپریشنز، مثال کے طور پر longاور double، جوہری نہیں ہیں۔ مزید تفصیلات یہاں پڑھی جا سکتی ہیں: 64-بٹ اقدار کو پڑھتے اور لکھتے وقت جوہری صلاحیت کو یقینی بنائیں ۔ جوہری سے متعلق مسائل کو اس مثال میں دیکھا جا سکتا ہے:
public class App {
    public static int value = 0;
    public static AtomicInteger atomic = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                value++;
                atomic.incrementAndGet();
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
        Thread.sleep(300);
        System.out.println(value);
        System.out.println(atomic.get());
    }
}
خصوصی AtomicIntegerطبقہ ہمیشہ ہمیں 30,000 دے گا، لیکن valueوقتاً فوقتاً بدلتا رہے گا۔ اس موضوع کا ایک مختصر جائزہ ہے: جاوا میں جوہری متغیرات کا تعارف ۔ "موازنہ اور تبادلہ" الگورتھم جوہری کلاسوں کے مرکز میں ہے۔ آپ اس کے بارے میں مزید یہاں لاک فری الگورتھم کے موازنہ میں - CAS اور FAA JDK 7 اور 8 کی مثال پر یا ویکیپیڈیا پر موازنہ اور تبادلہ مضمون میں پڑھ سکتے ہیں۔ ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔  حصہ III - تعامل - 9

http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

ہوتا ہے - پہلے

ایک دلچسپ اور پراسرار تصور ہے جسے "پہلے ہوتا ہے" کہا جاتا ہے۔ دھاگوں کے اپنے مطالعہ کے حصے کے طور پر، آپ کو اس کے بارے میں پڑھنا چاہیے۔ واقع ہونے سے پہلے کا رشتہ اس ترتیب کو ظاہر کرتا ہے جس میں دھاگوں کے درمیان کارروائیاں دیکھی جائیں گی۔ اس کی بہت سی تفسیریں اور تفسیریں ہیں۔ یہاں اس موضوع پر سب سے حالیہ پیشکشوں میں سے ایک ہے: Java "Happens-Before" Relationships ۔

خلاصہ

اس جائزے میں، ہم نے دھاگوں کے باہمی تعامل کے بارے میں کچھ تفصیلات دریافت کی ہیں۔ ہم نے ان مسائل پر تبادلہ خیال کیا جو پیدا ہو سکتے ہیں، نیز ان کی شناخت اور انہیں ختم کرنے کے طریقے۔ موضوع پر اضافی مواد کی فہرست: ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ اول — عمل درآمد کے دھاگے ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ دوم — ہم آہنگی ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ چہارم — کال کے قابل، مستقبل، اور دوست ایک ساتھ بہتر: جاوا اور تھریڈ کلاس۔ حصہ V — ایگزیکیوٹر، تھریڈ پول، فورک/ جوائن بیٹر ایک ساتھ: جاوا اور تھریڈ کلاس۔ حصہ VI - آگ دور!
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION