Thread یا نخ
وقتی سیستم عامل می خواهد چند عمل را با هم همزمان انجام دهد، که در زبان برنامه نویسی جاوا اصطلاحا Concurrency یا همزمانی گفته میشود، نیاز دارد که این عمل را به cpu واگذار کند و این را میدانیم که cpu در آن واحد فقط یک پردازش را می تواند انجام دهد و پردازش ها به صورت سری انجام می شوند و پردازش موازی وجود ندارد(البته غیر از کامپیوترهایی که چندتا cpu دارند و multiprocessor هستند) برای حل این مشکل، از thread یا نخ استفاده کردند. Thread یا نخ یا ریسمان، رشته یا نخی از اجرا در برنامه است. ماشین مجازی جاوا به اپلیکیشن اجازه داده تا از نخ های چندگانه ی با اجرا همزمان داشته باشد.وقتی یک ماشین مجازی شروع به کار می کند، معمولا یک نخ واحد میزبان وجود دارد(که بطور نمونه متدی به نام main فراخوانی می کند) ماشین مجازی به اجرای نخ ها ادامه می دهد تا هر کدام از دو اتفاق زیر رخ دهد:
• متد exit کلاس Runtime فراخوانی شود و مدیر امنیت اجازه ی exit را دهد.
• همه ی نخ ها کشته شوند. چه با بازگشت(return) از متد run و چه با پرتاب(throw) یک استثنائی که از آنسوی متد run انتشار می یابد.
دو روش برای ایجاد یک نخ اجرایی جدید وجود دارد.
روش اول: کلاسی تعریف کنیم که زیر کلاس thread باشد و ویژگی های آن را به ارث ببرد. این زیر کلاس باید متد run از کلاس thread را override کند. یک نمونه از زیر کلاس میتوان تخصیص داده شود و start شود. برای مثال نخی که اعداد اول بزرگتر از یک مقدار تعیین شده را محاسبه می کند، می تواند به صورت زیر نوشته شود:
1 package second;
2
3 class PrimeThread extends Thread {
4 long minPrime;
5 PrimeThread(long minPrime) {
6 this.minPrime = minPrime;
7 }
8
9 public void run() {
10 // compute primes larger than minPrime
11 }
12 }
13
کد زیر یک نخ را می سازد و آن را شروع به اجرا می کند:2
3 class PrimeThread extends Thread {
4 long minPrime;
5 PrimeThread(long minPrime) {
6 this.minPrime = minPrime;
7 }
8
9 public void run() {
10 // compute primes larger than minPrime
11 }
12 }
13
PrimeThread p = new PrimeThread(143);
p.start();
روش دوم: کلاسی تعریف می کنیم که اینترفیس Runnable را implement کند. سپس آن کلاس متد run را implement می کند. یک نمونه از کلاس می تواند تخصیص داده شود. وقتی Thread ساخته می شود، یک آرگومان پاس داده می شود و start می شود. در زیر همان مثال بالا را اما به روش دوم می بینیم:p.start();
1 package second;
2
3 class PrimeRun implements Runnable {
4 long minPrime;
5 PrimeRun(long minPrime) {
6 this.minPrime = minPrime;
7 }
8
9 public void run() {
10 // compute primes larger than minPrime
11 }
12 }
13
کد زیر یک نخ ایجاد می کند و آن را start می کند:2
3 class PrimeRun implements Runnable {
4 long minPrime;
5 PrimeRun(long minPrime) {
6 this.minPrime = minPrime;
7 }
8
9 public void run() {
10 // compute primes larger than minPrime
11 }
12 }
13
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
هر نخ یک نام برای شناسایی دارد. ممکن است بیش از یک نخ دارای همان نام باشد. زمانیکه یک نخ ایجاد می شود، اگر یک نام تخصیص داده نشود، یک نام جدید برای آن ایجاد می شود.new Thread(p).start();
سازنده ها:
Thread():یک آبکجت Thread جدید اختصاص می دهد. این سازنده مانندThread(null, null, gname) عمل می کند. در اینجا gname نامی است که اخیرا داده شده.
Thread(Runnable target):
یک آبکجت Thread جدید اختصاص می دهد. این سازنده مانند Thread(null, target, gname) عمل می کند. در اینجا gname نامی است که اخیرا داده شده.
Thread(Runnable target, String name):
یک آبکجت Thread جدید اختصاص می دهد. این سازنده مانند Thread(group, null, name) عمل می کند. در اینجا gname نامی است که اخیرا داده شده.
Thread(String name):
یک آبکجت Thread جدید اختصاص می دهد. این سازنده مانند Thread(null, null, name) عمل می کند.
Thread(ThreadGroup group, Runnable target):
یک آبکجت Thread جدید اختصاص می دهد. این سازنده مانند Thread(group, target, gname) عمل می کند.
Thread(ThreadGroup group, Runnable target, String name):
یک آبکجت Thread جدید اختصاص می دهد که target را به عنوان آبجکت run و name را به عنوان نام دارد. و متعلق به گروه نخ است که به وسیله ی group ارجاع داده می شود.
Thread(ThreadGroup group, Runnable target, String name, long stackSize):
یک آبکجت Thread جدید اختصاص می دهد که target را به عنوان آبجکت run و name را به عنوان نام دارد. و متعلق به گروه نخ است که به وسیله ی group ارجاع داده می شود و یک سایز پشته هم دارد.
Thread(ThreadGroup group, String name):
یک آبکجت Thread جدید اختصاص می دهد.
بعضی از متد ها:
وقتی" . Thread" را تایپ می کنیم و دکمه های ctrl+space را باهم بزنیم ، متد ها را نشان می دهد:
checkAccess(): تعیین می کند نخ در حال اجرای جاری اجازه اصلاح این نخ را دارد.
destroy(): این نخ را از بین می برد.
getName(): نام نخ را برمی گرداند.
getPriority(): اولویت نخ را برمی گرداند.
interrupt(): به نخ وقفه می دهد.
interrupted(): امتحان می کند که آیا نخ جاری وقفه یافته.
isAlive(): امتحان می کند که آیا این نخ برقرار است.
isInterrupted(): امتحان می کند که آیا این نخ وقفه یافته.
join(): منتظر می ماند تا این نخ کشته شود.
run(): اگر این نخ با استفاده از یک Runnable جداگانه ساخته شود، سپس آبجکت Runnable متد را فراخوانی می کند. وگرنه این متد کاری را انجام نمی دهد و چیزی را بر نمی گرداند.
setName(String name): نام نخ را به نامی که عنوان آرگومان گرفته، تغییر می دهد.
setPriority(int newPriority): اولویت نخ را تغییر می دهد.
sleep(long millis): موجب می شود که نخ در حال اجرای جاری، به اندازه ی عددی(میلی ثانیه) که گرفته به خواب رود. برای یک مدت زمان مشخص از لیست پردازش ها حذف می کند. نخ ها به صورت یک صف قرار می گیرند و ترتیب دریافت cpu براساس priority آنها است(درtaskmanager می توان priority آنها را تنظیم کرد).
sleep(long millis, int nanos): موجب می شود که نخ در حال اجرای جاری، به اندازه ی عددی که با میلی ثانیه نانو ثانیه گرفته به خواب رود.
start(): موجب می شود که نخ شروع به اجرا کند. ماشین مجازی جاوا متد run از نخ را فراخوانی می کند.
stop(): این متد به طور ذاتی خطرناک و ناامن است. یک نخ را با نخ دیگر متوقف می کند. توقف باعث می شود که همهی مانیتورهایی که قفل شده را باز کند.
نکته : کلاس thread در فضای نامی (namespace) java.lang قرار دارد.
java.lang.Object
java.lang.Thread
اکنون برای فهم بهتر مثالی را در محیط intellij بررسی می کنیم:
در این مثال فرض می کنیم دو حلقه ی for داریم که هر کدام چیزی را چاپ کند و کد های آن در زیر آمده است. ما می خواهیم این دوحلقه همزمان اجرا شود. در حالت عادی این دوحلقه نمی تواند به طور همزمان اجرا شود یعنی ابتدا حلقه ی اول اجرا می شود و تمام می شود و بعد از آن دومی اجرا می شود.
1 package first;
2 import java.sql.SQLOutput;
3 public class Threadexample {
4
5 public static void main(String[] args) {
6
7 for(int i=0; i<100;i++){
8 System.out.println("www.rasekhoon.net");
9 }
10
11 for(int j=0; j<100; j++){
12 System.out.println("habibollah alikhani");
13 }
14 }
15 }
اما برای اجرای همزمان این دو پروسه از thread استفاده می کنیم:2 import java.sql.SQLOutput;
3 public class Threadexample {
4
5 public static void main(String[] args) {
6
7 for(int i=0; i<100;i++){
8 System.out.println("www.rasekhoon.net");
9 }
10
11 for(int j=0; j<100; j++){
12 System.out.println("habibollah alikhani");
13 }
14 }
15 }
همانطور که می دانیم این کلاس در فضای نامی java.lang وجود دارد و نیازی به import کردن چیزی ندارد. و همچنین می دانیم که اینترفیس یک شکل استانداردی از یک کلاس می باشد که متدی که مد نظر ماست را داشته باشد. در اینجا از اینترفیس Runnable استفاده می کنیم. اگر دکمه ی ctrl را نگه داشته باشیم و روی کلمه ی Runnable کلیک کنیم، ما را به اینترفیس آن می برد که می بینیم در آن فقط یک متد abstract به نام run() وجود دارد. پس نتیجه می گیریم که کلاسی که می خواهد عملیات threading را انجام دهد، حتما باید متد run را پیاده سازی کند.
پس اگر چند کار داشته باشیم و بخواهیم همزمان باهم اجرا شود(در اینجا دوحلقه ی for) ، هریک از کار ها را در کلاسی جداگانه که اینترفیس Runnable را implement می کند، انجام می دهیم البته آنها را در متد run() قرار می دهیم. پس در این مثال ما دو کلاس به نام های for1 و for2 داریم. این کلاس ها را باید به دو thread ارسال کنیم. در متد main دو نمونه به نام های t1 و t2 از کلاس thread ایجاد می کنیم و به t1 نمونه ای از کلاس for1 و به t2 نمونه ای از کلاس for2 پاس می دهیم و سپس هر کدام از نخ های t1 و t2 را استارت می کنیم. نکته ی دیگر اینکه برای اجرا سیسم عامل (ماشین مجازی جاوا) به هرکدام از نخ ها 1 میلی ثانیه وقت می دهد تا اجرا شود و این زمان برای انجام این حلقه ها زیاد است و یک نخ کارش را تمام می کند و بعد سراغ نخ بعدی می رود البته اگر تعداد دور حلقه ها را بیشتر از 100 کنیم خروجی تغییر می کند اما اگر بخواهیم به خروجی ها نظم دهیم باید خواب نخ ها را تنظیم کنیم تا در زمان خاصی بخوابند و در زمان خاصی بیدار شوند. اگر عددی(بر حسب میلی ثانیه) که برای خواب آنها در نظر می گیریم کم باشد، هیچ فرقی نمی کند و دوباره در صف نخ ها قرار می گیرد پس ما از عدد بزرگتری استفاده می کنیم(100 میلی ثانیه) تا باعث شود نخ ها خارج از صف قرار گیرند و صف کار خودش را انجام می دهد و در زمان خودش آنها را بیدار می کند و صف تاثیری در کار آنها نمی گذارد.
به محض اینکه دستور sleep را برای نخ می نویسیم پیغامی می دهد که آن را در try/catch قرار دهیم و ما این کار را می کنیم.
کد ها به صورت زیر می باشد:
1 package Thread;
2 import java.sql.SQLOutput;
3 public class Texample {
4
5 public static void main(String[] args) {
6
7 Thread t1 = new Thread(new for1());
8 Thread t2 = new Thread(new for2());
9 t1.start();
10 t2.start();
11 }
12
13 public static class for1 implements Runnable{
14
15 public void run(){
16 for(int i=0; i<100;i++){
17 System.out.println("www.rasekhoon.net");
18 try{
19 Thread.sleep(100);
20 }
21 catch(Exception ex){
22 }
23 }
24 }
25 }
26 public static class for2 implements Runnable{
27
28 public void run(){
29 for(int j=0; j<100; j++){
30 System.out.println("habibollah alikhani");
31 try{
32 Thread.sleep(100);
33 }
34 catch(Exception ex){
35 }
36 }
37 }
38 }
39 }
40
و خروجی هم به صورت زیر می شود:2 import java.sql.SQLOutput;
3 public class Texample {
4
5 public static void main(String[] args) {
6
7 Thread t1 = new Thread(new for1());
8 Thread t2 = new Thread(new for2());
9 t1.start();
10 t2.start();
11 }
12
13 public static class for1 implements Runnable{
14
15 public void run(){
16 for(int i=0; i<100;i++){
17 System.out.println("www.rasekhoon.net");
18 try{
19 Thread.sleep(100);
20 }
21 catch(Exception ex){
22 }
23 }
24 }
25 }
26 public static class for2 implements Runnable{
27
28 public void run(){
29 for(int j=0; j<100; j++){
30 System.out.println("habibollah alikhani");
31 try{
32 Thread.sleep(100);
33 }
34 catch(Exception ex){
35 }
36 }
37 }
38 }
39 }
40
نکته: در کد ها ما ، Thread.sleep() نوشتیم و t1.sleep() و یا t2.sleep() ننوشتیم چون همیشه در یک لحظه فقط یک نخ اجرا می شود و Thread همیشه به نخ جاری اشاره می کند.
نکته: کلاس main و کلاسهای for1 و for2 ، هر سه در کلاس Texample تعریف شده اند و در صورتی می توان از for1 و for2 در کلاس main استفاده کرد که for1 و for2 هم به صورت static تعریف شوند وگرنه به صورت زیر خطا می گیرد:
استفاده از این مقاله با ذکر منبع راسخون بلامانع می باشد.
/ج