۲۹ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

رفتار رشته در JVM

JVM کاری را که می‌خواهد انجام می‌دهد، بنابراین چگونه می‌توانید ترتیب اجرای thread را پیش‌بینی کنید؟

JVM کاری را که می‌خواهد انجام می‌دهد، بنابراین چگونه می‌توانید ترتیب اجرای thread را پیش‌بینی کنید؟

Threading به تمرین اجرای همزمان فرآیندهای برنامه نویسی برای بهبود عملکرد برنامه اشاره دارد. در حالی که کار با رشته ها به طور مستقیم در برنامه های تجاری معمول نیست، آنها همیشه در چارچوب های جاوا استفاده می شوند. به عنوان مثال، چارچوب هایی که حجم زیادی از اطلاعات را پردازش می کنند، از رشته ها برای مدیریت داده ها استفاده می کنند. دستکاری رشته‌ها یا فرآیندهای CPU به طور همزمان عملکرد را بهبود می‌بخشد و در نتیجه برنامه‌های سریع‌تر و کارآمدتری ایجاد می‌کند.

این مقاله شما را با برخی از اصول رشته های سنتی جاوا و اجرای رشته در ماشین مجازی جاوا آشنا می کند. InfoWorld مقدمه Project Loom را ببینید تا در مورد رشته های مجازی و مدل جدید همزمانی ساختاریافته جاوا بیاموزید.

اولین رشته خود را پیدا کنید: روش main() جاوا

حتی اگر هرگز مستقیماً با رشته‌های جاوا کار نکرده‌اید، به‌طور غیرمستقیم با آنها کار کرده‌اید زیرا روش ()main حاوی رشته اصلی. هر زمان که روش main() را اجرا کردید، Thread اصلی را نیز اجرا کرده اید.

مطالعه کلاس Thread برای درک نحوه عملکرد رشته در برنامه های جاوا بسیار مفید است. همانطور که در اینجا نشان داده شده است، می توانیم با فراخوانی روش currentThread().getName() به رشته ای که در حال اجرا است دسترسی داشته باشیم:


public class MainThread {

    public static void main(String... mainThread) {
        System.out.println(Thread.currentThread().getName());
    }

}

این کد “اصلی” را چاپ می‌کند و موضوعی را که در حال حاضر اجرا می‌شود، شناسایی می‌کند. دانستن نحوه شناسایی رشته در حال اجرا اولین گام برای جذب مفاهیم رشته است.

چرخه حیات رشته جاوا

هنگام کار با نخ ها، مهم است که از وضعیت نخ آگاه باشید. چرخه حیات رشته جاوا از شش حالت رشته تشکیل شده است:

  • جدید: یک Thread() جدید ایجاد شده است.
  • قابل اجرا: روش start() Thread فراخوانی شده است.
  • در حال اجرا: روش start() فراخوانی شده است و رشته در حال اجرا است.
  • Suspended: رشته به طور موقت به حالت تعلیق درآمده است و می تواند توسط رشته دیگری از سر گرفته شود.
  • مسدود شده: موضوع منتظر فرصتی برای اجرا است. این زمانی اتفاق می‌افتد که یک رشته قبلاً متد synchronized() را فراخوانی کرده باشد و رشته بعدی باید تا پایان آن صبر کند.
  • خاتمه یافت: اجرای رشته کامل شد.

نمودار شش مرحله چرخه حیات رشته جاوا.

شکل ۱. شش حالت چرخه حیات رشته های جاوا

چیزهای بیشتری برای کاوش و درک در مورد وضعیت های رشته وجود دارد، اما اطلاعات موجود در شکل ۱ در حال حاضر کافی است.

توسعه کلاس موضوع

در ساده ترین حالت، پردازش همزمان با گسترش یک کلاس Thread انجام می شود، همانطور که در اینجا نشان داده شده است:


public class InheritingThread extends Thread {

    InheritingThread(String threadName) {
        super(threadName);
    }

    public static void main(String... inheriting) {
        System.out.println(Thread.currentThread().getName() + " is running");

        new InheritingThread("inheritingThread").start();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }
}

در اینجا، ما دو رشته را اجرا می کنیم: MainThread و InheritingThread. وقتی روش start() را با inheritingThread() جدید فراخوانی می کنیم، منطق در روش run() اجرا می شود.

ما همچنین نام رشته دوم را در سازنده کلاس Thread ارسال می کنیم، بنابراین خروجی این خواهد بود:


main is running.
inheritingThread is running.

رابط Runnable

به‌جای استفاده از وراثت، می‌توانید هدف قابل اجرا. عبور Runnable در سازنده Thread منجر به اتصال کمتر و انعطاف پذیری بیشتر می شود. پس از عبور از Runnable، می‌توانیم متد start() را دقیقاً مانند مثال قبلی فراخوانی کنیم:


public class RunnableThread implements Runnable {

    public static void main(String... runnableThread) {
        System.out.println(Thread.currentThread().getName());

        new Thread(new RunnableThread()).start();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

}

رشته های غیر شبح در مقابل دیمون

از نظر اجرا، دو نوع رشته وجود دارد:

  • رشته های غیر دیمون تا انتها اجرا می شوند. thread اصلی نمونه خوبی از یک نخ غیر دیمون است. کد موجود در main() همیشه تا پایان اجرا خواهد شد، مگر اینکه System.exit() برنامه را مجبور به تکمیل کند.
  • یک رشته شبح برعکس است، اساساً فرآیندی است که تا پایان لازم نیست اجرا شود.

قاعده را به خاطر بسپارید: اگر یک رشته غیر شبح محصور قبل از یک رشته شبح پایان یابد، رشته دیمون تا پایان اجرا نخواهد شد.

برای درک بهتر رابطه thread های daemon و non-daemon، این مثال را مطالعه کنید:


import java.util.stream.IntStream;

public class NonDaemonAndDaemonThread {

    public static void main(String... nonDaemonAndDaemon) throws                        InterruptedException {
        System.out.println("Starting the execution in the Thread " +      Thread.currentThread().getName());

        Thread daemonThread = new Thread(() ->      IntStream.rangeClosed(1, 100000)
                .forEach(System.out::println));

        daemonThread.setDaemon(true);
        daemonThread.start();

        Thread.sleep(10);

        System.out.println("End of the execution in the Thread " +    
                                           Thread.currentThread().getName());
    }

}

در این مثال من از یک رشته شبح برای اعلام محدوده ای از ۱ تا ۱۰۰۰۰۰، تکرار همه آنها و سپس چاپ استفاده کرده ام. اما به یاد داشته باشید، اگر رشته اصلی غیر دیمون ابتدا به پایان برسد، یک نخ شبح اجرا نمی شود.

خروجی به صورت زیر انجام می شود:

  1. شروع اجرا در رشته اصلی.
  2. اعداد از ۱ تا احتمالاً ۱۰۰۰۰۰ را چاپ کنید.
  3. پایان اجرا در رشته اصلی، به احتمال زیاد قبل از تکرار تا ۱۰۰۰۰۰ کامل می شود.

خروجی نهایی به اجرای JVM شما بستگی دارد.

همانطور که می بینید، رشته ها غیرقابل پیش بینی هستند.

کد منبع را دریافت کنید

کد را برای این چالش جاوا دریافت کنید. شما می توانید تست های خود را در حالی که از مثال ها پیروی می کنید، اجرا کنید.

اولویت موضوع و JVM

این امکان وجود دارد که اجرای رشته را با روش setPriority اولویت بندی کنید، اما، دوباره، نحوه مدیریت آن به پیاده سازی JVM بستگی دارد. لینوکس، macOS و Windows همگی پیاده‌سازی‌های JVM متفاوتی دارند و هر کدام اولویت رشته را طبق پیش‌فرض‌ها مدیریت می‌کنند.

اولویت رشته ای که تنظیم می کنید بر ترتیب فراخوانی رشته تأثیر می گذارد. سه ثابت اعلام شده در کلاس Thread عبارتند از:


     /**
    * The minimum priority that a thread can have.
     */
    public static final int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public static final int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public static final int MAX_PRIORITY = 10;

آزمایش‌ها را روی کد زیر امتحان کنید تا ببینید در نهایت با چه اولویتی اجرا می‌شوید:


public class ThreadPriority {

    public static void main(String... threadPriority) {
        Thread moeThread = new Thread(() -> System.out.println("Moe"));
        Thread barneyThread = new Thread(() -> System.out.println("Barney"));
        Thread homerThread = new Thread(() -> System.out.println("Homer"));

        moeThread.setPriority(Thread.MAX_PRIORITY);
        barneyThread.setPriority(Thread.NORM_PRIORITY);
        homerThread.setPriority(Thread.MIN_PRIORITY);

        homerThread.start();
        barneyThread.start();
        moeThread.start();
    }

}

حتی اگر moeThread را به‌عنوان MAX_PRIORITY تنظیم کنیم، نمی‌توانیم روی این موضوع حساب کنیم که ابتدا این رشته اجرا می‌شود. در عوض، ترتیب اجرا تصادفی خواهد بود.

یادداشتی در مورد ثابت ها در مقابل تعداد عدد

کلاس Thread با اولین نسخه جاوا معرفی شد. در آن زمان، اولویت ها با استفاده از ثابت ها تنظیم می شد، نه تعداد. با این حال، در استفاده از ثابت ها مشکلی وجود دارد: اگر عدد اولویتی را که در محدوده ۱ تا ۱۰ قرار ندارد ارسال کنیم، روش setPriority() یک IllegalArgumentException. امروز، می‌توانیم از enums برای حل این مشکل استفاده کنیم. استفاده از enum ها انتقال یک آرگومان غیرقانونی را غیرممکن می کند، که هم کد را ساده می کند و هم کنترل بیشتری بر اجرای آن به ما می دهد.

چه چیزی را در مورد موضوعات جاوا به خاطر بسپارید

  • روش start() را برای شروع یک Thread فراخوانی کنید.
  • امکان گسترش مستقیم کلاس Thread برای استفاده از رشته ها وجود دارد.
  • این امکان وجود دارد که یک عمل رشته را در یک رابط Runnable پیاده سازی کنید.
  • اولویت موضوع به پیاده سازی JVM بستگی دارد.
  • رفتار نخ به پیاده سازی JVM نیز بستگی دارد.
  • اگر یک رشته غیر شبح محصور ابتدا به پایان برسد، یک رشته شبح کامل نمی‌شود.

اشتباهات رایج در موضوعات جاوا

  • فراخوانی روش run() راهی برای شروع یک رشته جدید نیست.
  • دوبار تلاش برای شروع یک رشته باعث ایجاد IllegalThreadStateException می شود.
  • از اجازه دادن به چندین فرآیند برای تغییر وضعیت یک شی اجتناب کنید.
  • منطق برنامه‌ای را که به اولویت رشته متکی است ننویسید (نمی‌توانید آن را پیش‌بینی کنید).
  • به ترتیب اجرای thread متکی نباشید – حتی اگر یک نخ را ابتدا شروع کنید، هیچ تضمینی وجود ندارد که ابتدا آن را اجرا کنید.

چالش موضوعات جاوا را قبول کنید!

شما فقط چند چیز در مورد رشته های جاوا یاد گرفته اید، بنابراین بیایید یک چالش جاوا را امتحان کنیم تا آموخته های خود را آزمایش کنیم.


public class ThreadChallenge {
    private static int wolverineAdrenaline = 10;

    public static void main(String... doYourBest) {
        new Motorcycle("Harley Davidson").start();

        Motorcycle fastBike = new Motorcycle("Dodge Tomahawk");
        fastBike.setPriority(Thread.MAX_PRIORITY);
        fastBike.setDaemon(false);
        fastBike.start();

        Motorcycle yamaha = new Motorcycle("Yamaha YZF");
        yamaha.setPriority(Thread.MIN_PRIORITY);
        yamaha.start();
    }

    static class Motorcycle extends Thread {
        Motorcycle(String bikeName) { super(bikeName); }

        @Override public void run() {
            wolverineAdrenaline++;
            if (wolverineAdrenaline == 13) {
                System.out.println(this.getName());
            }
        }
    }
}

به نظر شما خروجی این کد چه خواهد بود؟ در اینجا گزینه ها وجود دارد:

A. هارلی دیویدسون
بی. دوج تاماهاوک
C. یاماها YZF
D. نامشخص

حل چالش

در کد بالا، سه رشته ایجاد کردیم. اولین رشته Harley Davidson است و ما به این رشته اولویت پیش‌فرض را اختصاص دادیم. رشته دوم Dodge Tomahawk است که به MAX_PRIORITY اختصاص داده شده است. سومین YZF یاماها، با MIN_PRIORITY است. سپس موضوعات را شروع کردیم.

برای تعیین ترتیب اجرای رشته‌ها، ابتدا باید توجه داشته باشید که کلاس Motorcycle کلاس Thread را گسترش می‌دهد و ما نام رشته را در آن ارسال کرده‌ایم. سازنده ما همچنین روش run() را با یک شرط لغو کرده‌ایم: if (wolverineAdrenaline == 13).

با وجود اینکه YZF یاماها سومین رشته در ترتیب اجرای ما است و دارای MIN_PRIORITY است، هیچ تضمینی وجود ندارد که برای همه پیاده‌سازی‌های JVM آخرین رشته اجرا شود.< /p>

همچنین ممکن است توجه داشته باشید که در این مثال ما رشته Dodge Tomahawk را به عنوان daemon تنظیم کرده‌ایم. از آنجا که این یک رشته شبح است، Dodge Tomahawk ممکن است هرگز اجرا را کامل نکند. اما دو رشته دیگر به طور پیش‌فرض غیر دیمون هستند، بنابراین رشته‌های Harley Davidson و YZF یاماها قطعا اجرای خود را کامل خواهند کرد.

برای نتیجه گیری، نتیجه D: نامشخص خواهد بود. این به این دلیل است که هیچ تضمینی وجود ندارد که زمان‌بندی رشته از ترتیب اجرا یا اولویت رشته ما پیروی کند.

به یاد داشته باشید، نمی‌توانیم برای پیش‌بینی ترتیب اجرای JVM به منطق برنامه (ترتیب رشته‌ها یا اولویت رشته‌ها) تکیه کنیم.

چالش ویدیویی! اشکال زدایی آرگومان های متغیر

اشکال‌زدایی یکی از ساده‌ترین راه‌ها برای جذب کامل مفاهیم برنامه‌نویسی و در عین حال بهبود کد شماست. در این ویدیو می‌توانید در حین اشکال‌زدایی و توضیح چالش رفتار رشته، همراه باشید:

درباره جاوا بیشتر بدانید

شاید به این مطالب علاقمند باشید