سلام
من اینجا یه اشتباهی مییبینم، شما هم میبینید؟ یا اصلاً اشتباه میبینم :|
Capture.jpg
سلام
من اینجا یه اشتباهی مییبینم، شما هم میبینید؟ یا اصلاً اشتباه میبینم :|
Capture.jpg
سلام.
منظورتون رو از اشتباهی که میبینید واضح بگید که باهم بررسیش کنیم. یه مثال ساده از الگوی Singleton هست و تضمین میکنه که فقط و فقط یک نمونه از این کلاس ساخته خواهد شد.
دوتا اشکال من در این کد میبینم. یکی در متد getInstance رخ داده که باید me__ رو برمیگردوند. توی متد someMethod هم با استفاده کردن از توضیح تک خطی کل اون خط تبدیل به متن توضیحی میشه که نویسندهی این کد حواسش بشون نبوده. صلاحیت چنین فردی واسه آموزش دادن الگوی Singleton اونم در فضای مجازی زیرسؤاله!
public class MySingleton {
// placeholder for current singleton object
private static MySingleton __me = null;
// private constructor - now no other object can instantiate
private MySingleton() { }
// this is how you ask for the singleton
public static MySingleton getInstance() {
// do I exist?
if ( __me == null ) {
// if not, instantiate and store
__me = new MySingleton();
}
return __me;
}
// additional functionality
public /* EDIT: */ void someMethod() { /* ... */ }
}
نکته: بهتره موقع قرار دادن کد از تگ کد جاوا استفاده کنید و از کد مورد نظرتون عکس نگیرید. اینجوری هم یکپارچگی انجمن حفظ میشه و هم برای افرادی که احتمالا در آینده اینجا رو بخونن مفید خواهد بود.
آخرین ویرایش به وسیله محمد فدوی : سه شنبه 01 اردیبهشت 1394 در 23:15 عصر
اگر به بهداشت و سلامت حیوانات علاقه دارید، از vetMD.ir دیدن کنید.
وبلاگ شخصی من: fadavi.net
اینجا کمتر سر میزنم. (تلگرام من)
برای اون متد someMethod هم اگه یه نوع بازگشتی یا void تعریف می کرد قابل قبول تر بود ، الان مثل کانستراکتور می مونه
Write Once, Run Anywhere
در ضمن این کلاس الان thread-safe نیست. اگه بخواد باشه، باید synchronization رعایت بشه:
public static syncrhonized MySingleton getInstance() {
// do I exist?
if ( __me == null ) {
// if not, instantiate and store
__me = new MySingleton();
}
return __me;
}
یا از اون بهتر و با performance بالاتر:
private static final Object _lock = new Object();
public static MySingleton getInstance() {
// do I exist?
if ( __me == null ) {
synchronized (_lock) {
if (__me == null) {
// if not, instantiate and store
__me = new MySingleton();
}
}
}
return __me;
}
اگر ممکنه یکی بگه singleton چیه و به چه کاري میاد؟
Singleton یک Design Pattern هست که توسط اون کلاسی ساخته میشه که تضمین میشه که تنها یک instance از اون وجود داره، و همهی بخشهای کد به اون به راحتی دسترسی دارن. یه مثالش connection به یه سیستم دیگه هست (مثل پایگاه داده). یعنی اگه شما میخواین تضمین کنید که توی برنامهتون تنها و تنها یک connection به یه سرویس خاص وجود داره، میتونید برای اون connection یه کلاس Singleton بنویسید.
روشش هم به این صورت هست که سازندهی کلاس رو private میکنید (در نتیجه هیچ کس به جز متدهای static خود کلاس نمیتونه ازش شیء بسازه) و یک نمونهی static از این کلاس داخل خودش میسازید، و یک متد getInstance تعریف میکنید (static) که این نمونه رو برمیگردونه. در نتیجه تنها یک نمونه از کلاس شما ساخته شده و به همه برگردونده میشه، و از همه جای کد به راحتی میشه این تابع static رو فراخوانی کرد و به نمونهی ساخته شده دسترسی پیدا کرد. یعنی نیازی نیست که یک پارامتر از نوع این کلاس به این طرف و اون طرف بفرستید و هر کسی نیاز داشته باشه، میتونه از طریق این تابع static بهش دسترسی پیدا کنه.
خوب از این توضیح آخری که دادم، مشخص میشه که Singleton خیلی مشابهت داره با متغیرهای global. در نتیجه همون بحثها و مشکلاتی که استفاده از متغیرهای global به وجود میاره، استفاده از Singleton هم به وجود میاره (البته Singleton خیلی بهتر از متغیرهای global هست).
توضیحات کامل در ویکیپدیا:
https://en.wikipedia.org/wiki/Singleton_pattern
پ.ن. یه اشکال دیگه توی کد مورد نظر (که حواسم بهش نبود) اینه که متغیر me__ باید volatile باشه. توضیح کاملتر در مورد volatile:
http://stackoverflow.com/questions/1...eyword-in-java
http://www.javamex.com/tutorials/syn...volatile.shtml
پ.ن.۲. توی مثال دومی که زدم، اون روشی که synchronized رو اورده بودم داخل if، اسمش هست Double-checked locking:
https://en.wikipedia.org/wiki/Double-checked_locking
سلام
علاوه بر توضیحات دوستمون یه سری نکات رو هم من بگم.
درباره design pattern ها بهترین توضیحات رو میتونید از کتاب Gang of Four بخونید.
موارد استفاده singleton خیلی زیاده. اما بعضی جاها استفادش بی مورده که اصطلاحا بهش anti-pattern میگن.
یه مورده استفاده خوب از singleton object ها، توی معماری mvc ه. مثلا در لایه model یه سری کلاس داری که وظایفی مثل CRUD رو برای هر entity انجام میدن.
فرض کنیم قراره در هر ثانیه هزاران نفر درخواست بفرستن به سرور و یه رکورد از یه تیبلی رو بخوان. اونوقت شما میای هربار اون کلاسی که وظیفش گرفتن آبجکت از دیتابیسه رو new میکنی و بقیه ماجرا...
خب اینجا یه فاجعه رخ میده، یعنی؛ به ازای هر درخواست، یبار new کردن یه آبجکت نسبتا سنگین.
پس بهتره که اون آبجکت singleton باشه. اما singleton بودن هم مشکلات خاص خودشو داره.
یکیش thread-safe بودنه.
یه راه خیلی بدش اینه که بیای توابعت رو synchronized تعریف کنی و برای دسترسی متغیرها lock بزاری و ...
اما راه خیلی بهترش اینه که اون کلاس رو به صورت stateless پیاده کنی.
برای اینکه کلاسی رو stateless کنی، باید تمامی property های سطح کلاس رو حذف کنی (به جز مواردی که مشکل ساز نمیشه). یعنی هیچ چیز مشترکی بین thread هایی که قراره از اون آبجکت singleton استفاده کنن نباشه.
مثلا دوتا ترد همزمان بیان اون آبجکت singleton رو بگیرن و یه چیزی رو تغییر بدن!
در کل چنین کلاسی فقط باید دارای متد باشه.
اما شاید یکی بگه خب اگه قرار باشه یه آبجکت بین همه ترد ها شیر بشه که performance میاد پایین! اما در واقع چنین چیزی اتفاق نمیفته و کاهش سرعتی نداریم (به شرطی که موارد بالا رعایت بشه)
مورد دیگه اینکه، اگه قراره این کلاس فقط توش متد باشه و فقط ام یه دونه آبجکت قراره ازش ساخته بشه، چرا اصلا متدها رو static تعریف نکنیم و اصلا آبجکتی ازش نسازیم؟
جوابش اینه که بله میشه همچین کاری کرد! اتفاقا اینطوری performance هم میره بالاتر و متدها سریع تر کال میشن.
اما در اینصورت از خیلی چیزای دیگه محروم میشیم. مثلا ارث بری، پیاده سازی interface و abstract و ... که این موارد جز موارد ضروری برای طراحی معماری برنامس و انعطاف پذیری به کدمون میده یعنی میشه بعدا منطق رو عوض کرد و کلی مسائل دیگه...
مثالی که براش میشه زد مثلا کلاس java.lang.Math که اگر دقت کنید همه متدهاش static هست و هیچ property ای هم در سطح کلاس براش تعریف نشده (به جز چنتا فیلد static final مثل PI که مشکل ساز نیستن)
الان میشد این کلاسو singleton اش کنن و مثلا همچین چیزی داشتیم :
Math.getInstance().max(1,2);
اما واقعا هیچ دلیلی برای singleton تعریف کردن این کلاس وجود نداره، همونطور که اول هم گفتم، بعضی مواقع استفاده کردن از singleton یه چیز اضافس و anti-pattern ه.
ضمن تشکر از توضیحات عالی دوستمون، من هم یک نکته اضافه کنم:
در اینجور مواقع که یه شیء سنگین داریم که نمیخوایم هی new بشه، بهتره از یه Object-pool استفاده کنیم:
https://en.wikipedia.org/wiki/Object_pool_pattern
When it is necessary to work with a large number of objects that are particularly expensive to instantiate and each object is only needed for a short period of time, the performance of an entire application may be adversely affected. An object pool design pattern may be deemed desirable in cases such as these.
یعنی به خاطر این که تعداد Object هامون میخواد زیاد بشه، نباید به سراغ Singleton بریم، بلکه Singleton مال وقتیه که در اثر طراحیمون از یه کلاس فقط و فقط یک شیء مورد نیاز هست.
در واقع Singleton به خاطر راحتی استفاده ازش، خیلی راحت مورد سوء استفاده قرار میگیره و anti-pattern میشه.
سلام، بله کاملا درسته. اتفاقا میخواستم به این مورد هم اشاره کنم فراموش کردم.
بهترین مثال هاش همین کانکشن ها به دیتابیسه که در connection pool نگه داری و مدیریت میشن یا مثلا thread pool ها که نمونش تو پکیج java.util.concurrent هست و ...
آخرین ویرایش به وسیله ahmad.mo74 : جمعه 04 اردیبهشت 1394 در 15:50 عصر
فکر کنم برای این هست که در شرط اول if(__me==null) اگر null نبود که خب این آبجکت وجود داره و سریع return کنه و منتظر بلاک synchronization نمونه.
اما اگر null بود خب منتظر میمونه _lock آزاد بشه و اونوقت وارد ساخت شی برای __me بشه. اما ممکنه از زمانی که __me نال بوده تا زمانی که _lock آزاد بشه این آبجکت __me ساخته شده باشه و چون singelton هست نباید آبجکت دیگه ای ساخته بشه. خب اگر نبود که میسازیم و بعد هم return میکنیم. همچنین از این نظر خیالمون راحته که داخل synchronization دیگه ممکن نیست پس از چک کردن null بودن __me یک thread دیگه وارد این محدوده بشه.
میتونست شرط بیرونی رو نذاره ولی همونطور که خودشون هم نوشتن به خاطر performance بالاتر انجام شده چون اگر شرط بیرونی نباشه با اینکه قرار نیست شی جدیدی ساخته باشه ولی اگر کسی قبلا _lock رو اشغال کرده باشه، الکی باید منتظر بمونیم.
البته این بدبینانه ترین حالت ممکنه رو در نظر گرفته و خود singelton به صورت پیش فرض این مسئله رو در نظر نمیگیرم، مگر اینکه برناممون multithread باشه و به این صورت نباشه که قبل از ساخت چند thread یک بار getInstance کرده باشیم. اگر قبل از ساخت چند thread یکبار از getInstance استفاده کنیم، بقیش حله.
دقیقاً همینطوره که فرمودید. یعنی به خاطر اون اول ماجرا که هنوز شیء ساخته نشده، کلاً برای ابد درگیر یه lock-unlock الکی نشیم!
اسم این روش هست double-checked locking و یه روش شناخته شده هست. ولی با کمال تأسف و تأثر، باید به عرض برسونم، که این روش ممکنه درست کار نکنه:
The "Double-Checked Locking is Broken" Declaration
من به شخصه خیلی ناراحت شدم وقتی فهمیدم این روش ممکنه کار نکنه! :(
اما روشهای جایگزین برای مشکلاتی که گفته وجود داره، برای اطلاعات بیشتر به ویکیپدیای این موضوع مراجعه کنید. نمونه کدی که اونجا اورده رو اینجا هم میارم:
// Correct lazy initialization in Java
class Foo {
private static class HelperHolder {
public static final Helper helper = new Helper();
}
public static Helper getHelper() {
return HelperHolder.helper;
}
}
جالب بود و ممنون که مطلب رو تکمیل کردید.اسم این روش هست double-checked locking و یه روش شناخته شده هست. ولی با کمال تأسف و تأثر، باید به عرض برسونم، که این روش ممکنه درست کار نکنه:
The "Double-Checked Locking is Broken" Declaration
من به شخصه خیلی ناراحت شدم وقتی فهمیدم این روش ممکنه کار نکنه! :(
نمیشه داخل synchronization و بعد از ساخت آبجکت یک متد از اون آبجکت رو صدا بزنیم تا مشکل Lazy initialization برطرف بشه؟!
مثلا
if ( __me == null ) {
synchronized (_lock) {
if (__me == null) {
// if not, instantiate and store
__me = new MySingleton();
__me.noOperation();
}
}
}
نمیدونم این کار مشکل رو برطرف میکنه یا نه. چون کامپایلر که هیچ، JIT هم توی جاوا فعال هست و انقدر هوشمند هست که ممکنه کل اون فراخوانی رو حذف کنه! البته میتونیم توش یه عملیاتی بذاریم که از دید سیستم nop نباشه، ولی واقعاً nop باشه! مثلاً با مقایسهٔ زمان فعلی سیستم با یه عددی که مطمئن هستیم مال سالها پیش هست، یه چیزی رو توی خروجی چاپ کنیم:
if (System.currentTimeMillis() < 1000000000) {
System.out.println("OMG! This system's time is too old! ;)");
}
ولی باز مطمئن نیستم که مشکل اینطوری حل میشه یا نه! یه جملهٔ جالبی توی اون مقاله داره:
البته این معنیاش این نیست که آدم مغزش رو تعطیل کنه و هر ایدهای به ذهنش رسید بگه حتماً غلطه! ولی میشه این برداشت رو کرد که به این راحتی نمیشه حلش کرد!
It doesn't work
There are lots of reasons it doesn't work. The first couple of reasons we'll describe are more obvious. After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. Your fixes will not work: there are more subtle reasons why your fix won't work. Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons.
Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.
این خیلی بده یه چیزی که منطقا میخواهیم انجام بشه، به دلایلی انجام نشه یا به تاخیر بیفته.
میشه این موضوع رو شبیه سازی کنیم و در اجراهای بسیار زیادی، ببینیم آیا با این ترفندهایی که گفتیم باز هم ممکنه دو بار new آبجکت بشه.
بازم ممنون به خاطر نکاتی که به اشتراک گذاشتید
توجه کنید که با تمام این بحثها، به علت وجود cache در CPU، باید متغیر me__ رو به صورت volatile تعریف کنیم. این هم خودش به نوبهٔ خودش خیلی بده! چون متغیری که volatile باشه، رسماً cache رو پردازنده نابود میکنه و به ازای هر دسترسی به اون، یه دسترسی به حافظه (RAM) هم انجام میشه که به شدت کارآمدی رو پایین میاره. یعنی هر بار شما این تابع getInstance رو فراخوانی میکنید، باید معطل دسترسی به حافظه بشید (سرعت دسترسی به cache پردازنده قابل مقایسه با سرعت دسترسی به حافظه نیست). البته اینجا صحبت از نانوثانیه هست! یعنی این «به شدت کارآمدی رو پایین میاره» در حد چند نانوثانیه هست! خیلی نگران نباشید! :)
در ضمن استفاده از volatile امکان انجام برخی از انواع optimization های زمان compile رو هم از سیستم میگیره (مثلاً دیگه به راحتی نمیشه فراخوانی این تابع getInstance رو inline کرد).
منم یه راهکار دارم که تا به حال امتحانش نکردم: میشه با زمان مسئله رو حل کرد، یعنی مثلاً اگه آدم مطمئنه که حداکثر بعد از مثلاً یک ساعت از اجرای برنامه، حتماً این متغیر initialize شده، میشه بعد از یک ساعت دیگه از این متغیر volatile استفاده نکرد. البته این ایدهام خیلی اولیه هست و ممکنه توی اجراش به مشکل بخوریم!