وقتی مشغول طراحی وبسایت با استفاده از JSP/Servlet هستیم، با متدهای ()doGet و ()doPost و ()service به وفور برخورد داریم. و بحث Thread-Safety در مورد طراحی servletها بسیار مهم و حائز اهمیته، به این دلیل که کاربران به اپلیکیشن ما از هر نقطه از دنیا و در هر لحظه امکان دسترسی دارند و احتمال یا به عبارت بهتر امکان فراخوانی «دقیقا در یک لحظه» صفحه مورد نظر بسیار زیاده و برای جلوگیری از باگهای ناخواسته در این زمینه میبایست حتما سرولتهای ما thread safed باشن.
قبل از ادامه صحبت بد نیست که در مورد thread یه توضیحی بدم. Thread یک پروسه اجرای تکی برنامهس. به عبارت دیگه یک زنجیره پشت سر هم اجرای قسمتی از برنامه ماست. وقتی ما میگیم یه برنامه multithreadه به این معنی نیست که ۲ تا نسخه از برنامه به صورت همزمان در حال اجراس، بلکه برنامه یکبار فقط اجرا شده و چندین بار توسط threadهای مختلف قسمتهای مخلف اون دارن اجرا میشن.
وقتی برنامه threadهای مختلف داشته باشه عبارت Thread Safe به این تعریفه که وقتی threadهای مختلف یک بخش مشخص برنامه رو دارن اجرا میکنن مقداری از حافظه رو دارن به اشتراک میذارن و threadهای مختلف اون بخش مشخص حافظه رو میتونن بخونن، داخلش بنویسن. مثال زیر رو در نظر بگیرین:
چه جوری این مشکل رو حل کنیم؟
اولین راه حل این میتونه باشه که متغیرهایی که امکان خوانده و نوشتن شدن دارن رو به صورت عمومی تعریف نکنیم و فقط جایی که احتیاج به تعریف و مقداردهی داره اونارو ایجاد بکنیم. مثال بالا رو به صورت زیر تغییر میدیم:
راه حل دوم رو میتونین به وسیله synchronize کردن مقطعی ایجاد بکنین. یعنی اون بخشی از کد برنامه رو که احتمال میدین ممکنه باعث اختلال تو thread safety بشه رو داخل بلاک synchronize بذارین. به این صورت:
قبل از ادامه صحبت بد نیست که در مورد thread یه توضیحی بدم. Thread یک پروسه اجرای تکی برنامهس. به عبارت دیگه یک زنجیره پشت سر هم اجرای قسمتی از برنامه ماست. وقتی ما میگیم یه برنامه multithreadه به این معنی نیست که ۲ تا نسخه از برنامه به صورت همزمان در حال اجراس، بلکه برنامه یکبار فقط اجرا شده و چندین بار توسط threadهای مختلف قسمتهای مخلف اون دارن اجرا میشن.
وقتی برنامه threadهای مختلف داشته باشه عبارت Thread Safe به این تعریفه که وقتی threadهای مختلف یک بخش مشخص برنامه رو دارن اجرا میکنن مقداری از حافظه رو دارن به اشتراک میذارن و threadهای مختلف اون بخش مشخص حافظه رو میتونن بخونن، داخلش بنویسن. مثال زیر رو در نظر بگیرین:
public class NotThreadSafe() { private int variable = 0; public void doSomething() { System.out.println("1) variable: " + variable); variable += 1; System.out.println("2) variable: " + variable); } }شرایطی را در نظر بگیرین که Thread شماره ۱ الان تو خط در حال اجرای خط ۹ام برنامهس و در همین لحظه هم Thread شماره ۲ خط ۷ام برنامه رو اجرا کرده باشه. خروجی که خواهیم داشت به این ترتیب میشه:
1) variable: 0 1) variable: 0 2) variable: 2 <--- 2) variable: 1و خب مشخصا ما انتظار این رو نداریم. اینجاس که به این کلاس Thread Safe نیست. یعنی threadهای مختلفی که به صورت نسبتا همزمان این کلاس رو صدا میزنن دارن روی همدیگه تاثیر میذارن.
چه جوری این مشکل رو حل کنیم؟
اولین راه حل این میتونه باشه که متغیرهایی که امکان خوانده و نوشتن شدن دارن رو به صورت عمومی تعریف نکنیم و فقط جایی که احتیاج به تعریف و مقداردهی داره اونارو ایجاد بکنیم. مثال بالا رو به صورت زیر تغییر میدیم:
public class IsThreadSafe() { public void doSomething() { private int variable = 0; System.out.println("1) variable: " + variable); variable += 1; System.out.println("2) variable: " + variable); } }همونطور که میبینین ما variable رو به جای اینکه تو scope کلاس تعریف کنیم، تو scope متد ()doSomething تعریف کردیم. بنابراین variable بین threadهای مختلف کلاس مشترک نیست. بلکه داخل متدی که تعریف شده مشترکه. که محتویات این داخل هم اثر روی threadهای دیگه نمیذاره.
راه حل دوم رو میتونین به وسیله synchronize کردن مقطعی ایجاد بکنین. یعنی اون بخشی از کد برنامه رو که احتمال میدین ممکنه باعث اختلال تو thread safety بشه رو داخل بلاک synchronize بذارین. به این صورت:
public class IsThreadSafe() { private int variable = 0; private String mutex = ""; public void doSomething() { synchronized (mutex) { System.out.println("1) variable: " + variable); variable += 1; System.out.println("2) variable: " + variable); } } }به این ترتیب مشکل ما نیز حل میشه ولی یه نکته خیلی مهم وجود داره که هر بخشی که داخل بلاک synchronize قرار بگیره فقط و فقط توسط یه thread در آن واحد امکان اجرا داره. و برای یه برنامه که به صورت multithread آماده شده JVM بقیه threadها رو مجبور به صبر میکنه که threadی که الان داخل اون بلاک قرار داره کارش تموم شه و از اون خارج بشه و بعد thread بعدی وارد اون بخش بشه. مشخصه که این وضعیت بعضی وقتها ممکنه مطلوب ما نباشه و باعث latency میشه.