Բազմահոսքայնությունը Java-ում

Բազմահոսքայնությունը ծրագրավորման տեխնոլոգիա է, որը հնարավորություն է տալիս ծրագրում ունենալ 2 և ավելի կտորներ, որոնք կարող են իրագործվել միաժամանակ՝ նույն ռեսուրսի հետ աշխատելու հնարավորությամբ։ Ամեն մի կտոր կարող է լուծել տարբեր խնդիր, դրանով հասանելի ռեսուրսների (օրինակ՝ մի քանի պրոցեսորի առկայություն) օգտագործումը դարձնելով ավելի օպտիմալ։

Մի քանի պրոցեսների կողմից ընդհանուր ռեսուրսի կիսելը կոչվում է բազմախնդրություն (multitasking)։ Բազմահոսքայնությունը ընդլայնում է բազմախնդրության գաղափարը և հնարավորություն է ստեղծում, բացի ՕՀ-ում զուգահեռ աշխատող ծրագրերից (պրոցեսներից), ունենալ նաև զուգահեռ ընթացող հոսքեր՝ մի ծրագրի մեջ։

Հոսքերի առաջնայնությունը խմբագրել

Java լեզվում ցանկացած հոսք ունի իր առաջնայնությունը, որը օգնում է ՕՀ-ին սահմանել դրանց իրագործման հերթականությունը։ Այդ առաջնայնությունը որոշվում է 1-10 միջակայքի թվերով, որը հոսք ստեղծելուց չնշելու դեպքում ավտոմատ կլինի 5։ Սակայն այնուամենայնիվ 100 տոկոսով երաշխավորված չէ, թե ինչ հերթականությամբ կիրագործվեն հոսքերը։ Այն հիմնականում կախված է ՕՀ-ի տեսակից։

Իրականացում խմբագրել

Java-ում դասը որպես հոսք իրագործելու համար տվյալ դասը պետք է իրականացնի Runnable ինտերֆեյսը՝ սահմանելով վերջինիս կողմից հայտարարված run մեթոդը, կամ ժառանգի Thread դասից և վերասահմանի

public void run()

public void start()

մեթոդները։

Հոսքը սկսելու համար պետք է կանչել start() մեթոդը։

class WikiThread implements Runnable {
    private Thread t;
    private String threadName;
 
    WikiThread(String name) {
        threadName = name;
        System.out.println("Creating " + threadName );
    }
 
    public void run() {
        System.out.println("Running " + threadName );
        try {
            for(int i = 4; i > 0; --i) {
                System.out.println("Thread: " + threadName + ", " + i);
                Thread.sleep(50);
            }
        }
        catch (InterruptedException e) {}
 
        System.out.println("Thread " + threadName + " exiting.");
    }
 
    public void start() {
        System.out.println("Starting " + threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            t.start();
        }
    }
}

public class Wiki {
    public static void main(String[] args) {
        WikiThread R1 = new WikiThread("Thread-1");
        R1.start();
 
        WikiThread R2 = new WikiThread("Thread-2");
        R2.start();
    } 
}

Thread դասից ժառանգելու օրինակ

class ThreadDemo extends Thread {
    private Thread t;
    private String threadName;
 
    ThreadDemo( String name) {
        threadName = name;
        System.out.println("Creating " + threadName );
    }
  
    public void run() {
        System.out.println("Running " + threadName );
        try {
            for(int i = 4; i > 0; ––i) {
                System.out.println("Thread: " + threadName + ", " + i);
                Thread.sleep(50);
            }
        }

        catch (InterruptedException e) {
            System.out.println("Thread " + threadName + " interrupted.");
        }
 
        System.out.println("Thread " + threadName + " exiting.");
    }
 
    public void start() {
       System.out.println("Starting " + threadName );
       if (t == null) {
           t = new Thread (this, threadName);
           t.start();
       }
    }
}

public class WikiThread {
    public static void main(String args[]) { 
        ThreadDemo T1 = new ThreadDemo("Thread-1");
        T1.start();
 
        ThreadDemo T2 = new ThreadDemo("Thread-2");
        T2.start();
    } 
}

Գործնականում այս երկու ձևերից ավելի նախընտրելի է առաջինը, քանի որ ժառանգում օգտագործվում է որևէ ֆունկցիոնալություն վերասահմանելու համար, ինչի անհրաժեշտությունը չլինելու դեպքում բավական է պարզապես իրականացնել Runnable ինտերֆեյսը։ Դա նաև հնարավորություն կտա իրականացնող դասին ծառանգել մեկ այլ դասից, քանի որ Java լեզուն չի տալիս բազմակի ժառանգման հնարավորություն, բայց թույլատրում է դասին իրականացնել ցանկացած թվով ինտերֆեյսներ միաժամանակ ժառանգելով մեկ դասից։

Մի քանի հոսքով ծրագրի օրինակ խմբագրել

Ստորև նշված օրինակում ստեղծված է 3 հոսք՝

RunnableThread հոսքի run() մեթոդում կանչված է sleep() մեթոդը, որը դադարեցնում է հոսքի աշխատանքը որոշ ժամանակով։ 
Երբ t1–ը կասեցվում է, t2–ը սկսում է իր աշխատանքը։ Ինչպես նաև t2–ը կանչում է join() մեթոդը, որը ստիպում է
ընթացիկ հոսքին սպասել մինչև t2–ի ավարտը։ t2–ի ավարտվելուն պես իր աշխատանքն է սկսում t3–ը։
class RunnableThread implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("Runnable Thread:"+Thread.currentThread().getName());
            Thread.sleep(5000);
        }
        catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }
}

public static void main(String[] args) {
    Thread t1 = new Thread(new RunnableThread());
    Thread t2 = new Thread(new RunnableThread());
    SingleThread t3 = new SingleThread();

    t1.start();
    t2.start();
    try {
        t2.join();
    }
    catch (InterruptedException e) {
        System.out.println(e.getMessage());
    }
    t3.start();
}

Կյանքի փուլերը խմբագրել

Հոսքը իր կյանքի ընթացքում կարող է լինել հետևյալ վեց վիճակներում՝

Հոսքի կյանքի փուլերը
նոր հոսք, որը դեռ չի սկսվել
աշխատող հոսք, որը իրագործվում է Ջավա Վիրտուալ Մեքենայի կողմից
արգելափակված հոսք․ որը սպասում է բացվելուն
սպասող հոսք, որը սպասում է մեկ այլ հոսքի աշխատանքի ավարտին
ժամանակով սպասող հոսք, որը սպասում է մեկ այլ հոսքի աշխատանքի ավարտին առավելագույնը t ժամանակ
ընդհատված հոսք, որի աշխատանքը ավարտվել է

Հոսքերի ժամանակավոր դադարեցում խմբագրել

Հոսքի աշխատանքը դադարեցնելու համար կարելի է տվյալ հոսքի հղումին վերագրել null

currentThread = null;

Հոսքի աշխատանքը դադարեցնելու համար խորհուրդ չի տրվում օգտվել լեզվի ստանդարտից հեռացված stop() մեթոդից։ Այս մեթոդի կանչը հանգեցնում է տվյալ հոսքի կողմից արգելափակված բոլոր հոսքերի բացվելուն։

Հոսքի աշխատանքը որոշակի ժամանակով դադարեցնելու «հոսքը քնեցնելու» համար օգտագործվում է Thread.sleep() մեթոդը։

Thread.sleep(5000);

Վերը նշված օրինակում մեթոդին տրված արգումենտը իրենից ներկայացնում է այն միլիվայրկյանների քանակը, որքան որ պետք է հոսքը քնի։ Քնի ռեժիմից հոսքին հանելու համար անհրաժեշտ է կանչել interrupt() մեթոդը։

Հոսքերի սինխրոնացում խմբագրել

Ծրագրում երկու և ավելի հոսքերի առկայության դեպքում կարող է ստեղծվել մի իրադրություն, որում երկու կամ ավելի հոսք փորձեն դիմել նույն ռեսուրսին կամ փոփոխել այն, ինչը կհանգեցնի անցանկալի արդյունքի։

Օրինակ երկու հոսք կարող են գրել նույն ֆայլի մեջ։ Նման խնդիրներից խուսապելու համար անհրաժետշ է սինխրոնիզացնել հոսքերի աշխատանքը այնպես, որ ապահովագրված լինի նման օրինակ տարաձայնությունների բացակայությունը։

Ջավայում դա իրականացված է օգտագործելով մոնիտորների գաղափարը։ Ցանկացած օբյեկտ ունի իր հետ ասոցացվող մոնիտոր, որը կարող է արգելափակվել կամ բացվել հոսքի կողմից։ Ժամանակի ցանկացած պահի միայն մեկ հոսք կարող է ունենալ տվյալ օբյեկտի մոնիտորի բանալին։

Ստորև ներկայացված է Ջավայի ծրագրի օրինակ առանց սինխրոնիզացիայի և սինխրոնիզացիայով։

//առանց սինխրոնիզացիա
class PrintDemo {
   public void printCount(){
    try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter   ---   "  + i );
         }
     } catch (Exception e) {
         System.out.println("Thread  interrupted.");
     }
   }
}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo( String name,  PrintDemo pd){
       threadName = name;
        PD = pd;
   }
   public void run() {
     PD.printCount();
     System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start ()
   {
      System.out.println("Starting " +  threadName );
      if (t == null)
      {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {
   public static void main(String args[]) {

      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD );
      ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD );

      T1.start();
      T2.start();

      // wait for threads to end
      try {
         T1.join();
         T2.join();
      } catch( Exception e) {
         System.out.println("Interrupted");
      }
   }
}
//սինխրոնիզացիայով

class PrintDemo {
   public void printCount(){
    try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter   ---   "  + i );
         }
     } catch (Exception e) {
         System.out.println("Thread  interrupted.");
     }
   }

}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo( String name,  PrintDemo pd){
       threadName = name;
       PD = pd;
   }
   public void run() {
     synchronized(PD) {
        PD.printCount();
     }
     System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start ()
   {
      System.out.println("Starting " +  threadName );
      if (t == null)
      {
         t = new Thread (this, threadName);
         t.start ();
      }
   }

}

public class TestThread {
   public static void main(String args[]) {

      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD );
      ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD );

      T1.start();
      T2.start();

      // wait for threads to end
      try {
         T1.join();
         T2.join();
      } catch( Exception e) {
         System.out.println("Interrupted");
      }
   }
}

Առաջին դեպքում ծրագիրը ամեն անգամ աշխատացնելուց հետո ստացվում են տարբեր արդյունքներ։ Երկրորդ դեպքում արդյունքները միշտ նույնն են։