Javaマルチスレッドベース

14328 ワード

マルチスレッドプログラムは低いレベルでマルチタスクの概念を広げた:1つのプログラムは同時に複数のタスクを実行する.通常、各タスクはスレッドと呼ばれ、スレッド制御の略称である.1つ以上のスレッドを同時に実行可能なプログラムは、通常、マルチスレッドプログラムと呼ぶ.では、マルチプロセスとマルチスレッドの違いは何でしょうか.本質的な違いは、各プロセスには独自の変数があり、マルチスレッドはデータを共有することである.これはリスクがあるように聞こえますが、共有変数により、スレッド間の通信がプロセス間の通信よりも効率的になり、容易になります.また、一部のオペレーティングシステムでは、プロセスに比べてスレッドが軽量レベルになり、1つのプロセスを起動するよりもスレッドを取り消すコストが少なくなります.実際の応用では、マルチスレッドは非常に有用であり、例えば、1つのブラウザが同時にいくつかのピクチャをダウンロードすることができ、1つのウェブサーバは同時に複数の同時要求を処理する必要があり、グラフィックユーザインタフェースプログラムは1つの独立したスレッドでホスト環境からユーザインタフェースのイベントを収集する.次にjavaアプリケーションにマルチスレッド能力を追加する方法について説明します.
Threadから継承:
class Player extends Thread  
{  
    String name;  
    Player(String name){  
        this.name=name;  
    }  
    @Override  
    public void run(){  
        for(int i=0;i<3;i++)  
            System.out.println(name+"     "+i);  
    }  
}  
  
public class ThreadTest {  
  
    public static void main(String []args){  
        Player aPlayer=new Player("  ");  
        Player bPlayer=new Player("   ");  
        aPlayer.start();  
        bPlayer.start();  
    }  
}  

走行結果:劉翔は走行中0ボルトは走行中0ボルトは走行中1ボルトは走行中2劉翔は走行中1劉翔は走行中2
Runnableインタフェースの実装
class Car implements Runnable  
{  
    String name;  
    Car(String name){  
        this.name=name;  
    }  
  
    @Override  
    public void run() {  
        for(int i=0;i<3;i++)  
            System.out.println(name+"     "+i);  
    }  
      
}  
  
public class ThreadTest {  
  
    public static void main(String []args){  
        Car aCar=new Car("   ");  
        Car bCar=new Car("  ");  
        Thread t1=new Thread(aCar);  
        Thread t2=new Thread(bCar);  
        t1.start();  
        t2.start();  
    }  
}  

運転結果:ベンツ運転中0フェラーリ運転中0フェラーリ運転中1フェラーリ運転中2ベンツ運転中1ベンツ運転中2
Threadからの継承とRunnableインタフェースの実装の違い

class Shop extends Thread  
{  
    String name;  
    Shop(String name){  
        this.name=name;  
    }  
    private int count=3;  
    @Override  
    public void run(){  
        for(int i=0;i<3;i++)  
            if(count>=0)  
                System.out.println(name+"     ,  "+--count);  
    }  
}  

public class ThreadTest {  
      
    public static void main(String []args){  
          
        Shop aShop=new Shop("  1 ");  
        Shop bShop=new Shop("  2 ");  
        aShop.start();  
        bShop.start();  
    }  
  
}  

運行結果:窓口1で1枚売り、残り2窓口2で1枚売り、残り2窓口1で1枚売り、残り1窓口2で1枚売り、残り1窓口1で1枚売り、残り0窓口2で1枚売り、残り0窓口2で0
class Shop implements Runnable  
{  
    private int count=5;  
    @Override  
    public void run(){  
        for(int i=0;i<20;i++)  
            if(this.count>=0)  
                System.out.println(Thread.currentThread().getName()+"     , "+count--);  
    }  
}  
  
public class ThreadTest {  
  
    public static void main(String []args){  
        Shop shop=new Shop();  
        new Thread(shop,"   ").start();  
        new Thread(shop,"   ").start();  
    }  
  
}  


運行結果:窓口で1枚売り、残り5窓口で2枚売り、残り4窓口で1枚売り、残り3窓口で2枚売り、残り2窓口で1枚売り、残り1窓口で2枚売り、残り1窓口で2枚売り、残り0
**mainメソッドもスレッドです.Javaではスレッドが同時に起動していますが、いつ、どちらが先に実行されるかは、CPUのリソースが先に得られるかによって決まります.**Javaでは、プログラムが実行されるたびに少なくとも2つのスレッドが起動されます.1つはmainスレッド、1つはゴミ収集スレッドです.Javaコマンドを使用してクラスを実行するたびに、実際にはJVMが起動されるため、各jVMは実際にオペレーティングシステムでプロセスを開始します.
スレッドの強制実行:
public class ThreadTest implements Runnable {  
  
    @Override  
    public void run(){  
        for(int i=0;i<3;i++)  
            System.out.println(Thread.currentThread().getName());  
    }  
      
    public static void main(String []args){  
          
        ThreadTest threadTest=new ThreadTest();  
        Thread thread=new Thread(threadTest,"  ");  
        System.out.println("       "+thread.isAlive());  
        thread.start();  
        System.out.println("       "+thread.isAlive());  
        for(int i=1;i<=5;i++)  
        {  
            if(i>3)  
            {  
                try{  
                    thread.join();  
                }catch(Exception e){e.printStackTrace();}  
            }  
            System.out.println("      "+i);  
        }  
    }  
}  

実行結果:スレッド実行中falseスレッド実行中trueメインスレッド実行中1メインスレッド実行中2メインスレッド実行中3スレッドスレッドスレッドスレッドメインスレッド実行中4メインスレッド実行中5
スレッドのスリープ:
public class ThreadTest implements Runnable {  
  
    @Override  
    public void run(){  
        for(int i=0;i<3;i++)  
        {  
            System.out.println("     "+i);  
            try{  
                Thread.sleep(1000);  
            }catch(Exception e){e.printStackTrace();}  
              
        }  
    }  
      
    public static void main(String []args){  
        ThreadTest threadTest=new ThreadTest();  
        new Thread(threadTest).start();  
    }  
}  

実行結果:スレッド実行中0スレッド実行中1スレッド実行中2
スレッド割込み:
スレッドのrunメソッドが実行され、return文によって返される場合、またはメソッドにキャプチャされていない例外が発生すると、スレッドは終了します.interruptメソッドでスレッドの中断を要求することもできます.スレッドに対してinterruptメソッドを呼び出すと、スレッドの割り込み状態がセットされます.各スレッドにはbooleanフラグがあり、各スレッドは時々このフラグをチェックして、スレッドが中断しているかどうかを判断しなければならない.次のようになります.
while(!Thread.currentThread().isInterrupted()&& more word to do){  
        do mo work;  
    }  

しかし、スレッドがブロックされている場合、割り込み状態を検出できません.ここでInterruptedExceptionは、ブロックされたスレッドがinterruptメソッドを呼び出すとInterruptedExceptionによって中断されます.中断されたスレッドを終了する必要はありません.中断されたスレッドは、中断を無視して、どのように中断するかを決定することができます.例外を処理した後も、中断を無視して実行を継続することができます.より一般的には、スレッドは、終了要求としてスレッドを単純に中断する.このrunメソッドは、次のようになります.
public void run()  
{  
    try  
    {  
        ...  
        while(!Thread.currentThread().isInterrupted()&& more work to do){  
            do more work;  
        }  
    }  
    catch(InterruptedException e){  
        //Thread was interrupted during sleep or wait  
    }  
    finally  
    {  
        cleanup,if required;  
    }  
    //exiting the run method teminates the thread  
}  

スレッドが割り込みステータスがセットされている間にsleepメソッドを呼び出すと、スリープではなく、割り込みステータスがクリアされ、InterruptedException例外が放出されます.
public class ThreadTest implements Runnable {  
  
    @Override  
    public void run(){  
        System.out.println("  run  ");  
        try {  
            Thread.sleep(10000);  
            System.out.print("      ");  
              
        } catch (InterruptedException e) {  
            System.out.println("       ");  
            return ;  
        }  
        finally{  
            System.out.println("    "+Thread.currentThread().isInterrupted());  
        }  
          
        System.out.println("      ");  
    }  
      
    public static void main(String []args){  
        ThreadTest threadTest=new ThreadTest();  
        Thread thread=new Thread(threadTest);  
        thread.start();  
        try {  
            Thread.sleep(2000);  
        } catch (Exception e) {e.printStackTrace();}  
        thread.interrupt();  
    }  
}  

実行結果:runメソッドを実行スレッドスリープ中断スレッド状態false
    void interrupt()  
        /*         。          true。      
         sleep    ,      InterruptedException*/  
    static boolean interrupted()  
        //            ,                   false  
    boolean isInterrupted() //         ,          
    static Thread currentThread() //         Thread    
public class ThreadTest implements Runnable {  
  
    @Override  
    public void run(){  
        for(;;)  
            System.out.println("     ");  
    }  
      
    public static void main(String []args){  
        ThreadTest threadTest=new ThreadTest();  
        Thread thread=new Thread(threadTest);  
        thread.setDaemon(true);  
        thread.start();  
    }  
}  

設定はデッドサイクルとなっていますが、デッドサイクルでのスレッド操作がバックグラウンドで実行されるように設定されているため、プログラムは実行できます.
スレッド同期:
public class ThreadTest implements Runnable {  
  
    private int ticket=5;  
    private int money=0;  
    @Override  
    public void run(){  
        String name=Thread.currentThread().getName();  
        while(ticket>=1){  
                sell();           
        }  
    }  
    //sell              
    public void sell(){  
        String name=Thread.currentThread().getName();  
        if(ticket>=1){  
            System.out.println(name+"     ");  
            ticket--;  
            //            ,                 
            for(int j=1;j<=1000;j++)  
                Math.sin(j);  
            money++;  
            System.out.println("ticket = "+ticket+"\t money= "+money);  
        }  
    }  
    public static void main(String []args){  
        ThreadTest threadTest=new ThreadTest();  
        new Thread(threadTest,"   ").start();;  
        new Thread(threadTest,"   ").start();  
    }  
}  

運行結果:窓口が売り始めた2窓口が売り始めたticket=3 money=2 ticket=3 money=2窓口が売り始めた2窓口が売り始めたticket=1 money=3 ticket=1 money=4窓口が売り始めた2窓口が売り始めたticket=-1 money=5 ticket=-1 money=6
その結果、スレッドが同期していないため、例えばticket=1の場合、ウィンドウのスレッド判定条件ticket>=1が実行sell部分に入り、まず「ウィンドウが最初に切符を売る」と印刷され、その後、その部分が完全に終了しなかったため、ticketはticket-部分を実行しなかったため、依然として1の場合、ウィンドウの2スレッドの実行の番になる可能性がある.判断条件ticket>=1でまたsell部分に入ったということは理解できますが、ticket=1の場合、両方のスレッドがsell部分に入っているので、当然ticket=-1ということが起こります.同期を実行するには、synchronizedキーワードを使用してsellメソッドを同期メソッドに設定する方法をいくつか使用して、上記のコードを変更できます.

        synchronized public void sell(){  
            //more code ...  
        }  

クラスにメンバー変数としてロックを追加し、sellメソッドを変更し、コードの一部を与えます.
// more code ...  
private Lock lock=new ReentrantLock();  
    public void sell(){  
        lock.lock();  
        try   
        {  
            if(ticket>=1){  
                ///more code ...  
            }  
        }   
        finally  
        {  
            lock.unlock();  
        }  
    }  
//more code ...   

同期を設定した後、実行結果:窓口がチケットを売り始めたticket=4 money=1窓口がチケットを売り始めたticket=3 money=2窓口がチケットを売り始めたticket=2 money=3窓口がチケットを売り始めたticket=1 money=4窓口がチケットを売り始めたticket=0 money=5
次は銀行振替の例です.プログラムの主な機能は、100口座があり、各口座に1000元の残高が予めあり、各口座にスレッドを開き、残高をランダムな口座に振り替えることです.
class Bank  
{  
      
    private double accounts[];  
      
    Bank(int n,double initMoney){  
        accounts=new double[n];  
        for(int i=0;i
public class ThreadTest implements Runnable  
{  
    private Bank bank;  
    private int fromAccount;//          
    private double maxAmount;//       
    private int DELAY=10;  
      
    public ThreadTest(Bank bank,int from,double max){  
        this.bank=bank;  
        fromAccount=from;  
        maxAmount=max;  
    }  
      
    @Override  
    public void run(){  
        try   
        {  
            while(true)  
            {  
                int toAccount=(int)(bank.getSize()*Math.random());  
                double amount=(long)(maxAmount*Math.random());  
                bank.transfer(fromAccount, toAccount, amount);  
                Thread.sleep((long)(DELAY*Math.random()));  
            }  
        } catch (InterruptedException e) {}  
    }  
      
    public static void main(String []args){  
          
        Bank bank=new Bank(100, 1000);  
        for(int i=0;i<100;i++)  
            new Thread(new ThreadTest(bank, i, 1000)).start();  
          
    }  
}  

同期が行われていない場合、当初は総額度に問題はなかったが、後期になると突然口座総額に問題があったことが判明した.なぜなら、転出口座残高の減少と転入口座残高の増加は原子操作ではないからである.
Thread-20     40.00 from 20 to 47 Total money = 100000.00
Thread-33    647.00 from 33 to 54 Total money = 100000.00
Thread-72    579.00 from 72 to 69 Total money = 100000.00
...
Thread-31    299.00 from 31 to 8 Total money =  99463.00
Thread-98    854.00 from 98 to 6 Total money =  99463.00

ロックを増やして臨界領域を設定し、transferメソッドを変更することができます.
//more code ...  
private Lock lock=new ReentrantLock();  
public void transfer(int from,int to ,double amount){  
      lock.lock();  
      try   
      {  
          if(accounts[from]

しかし,スレッドが臨界領域に入った後,実行するには条件を満たす必要があることが分かった.ロックを取得するも有効に動作しないプロセスを管理するための条件を設定する必要がある.しかし、単純にこのような操作を行うだけでは
    if(bank.getMoney(fromAccount)>=amount)  
            transfer(fromAccount,toAccount,amount);  

実行条件判断後にtransferメソッドを実行する前にスレッドが中断する可能性があるが、transferメソッドをこのように修正すると、ロックを取得したプロセスにおける残高が不足すると、他のアカウントが残高を転入するのを待つことになるが、すでにロックを取得しているため、他のアカウントが残高操作を行う機会がない.
public void transfer(int from,int to ,double amount){  
      lock.lock();  
      try   
      {  
          while(accounts[from]

条件オブジェクトを使用することができます.
//more code ...  
private Condition condition;  
public Bank(int n,double initMoney){  
    condition=lock.newCondition();  
    //more code ..  
}  
//more code ...  
public void transfer(int from,int to ,double amount){  
       lock.lock();  
       try   
       {  
          while(accounts[from]<=amount)  
              condition.await();  
          //more code ...  
           condition.signalAll();  
       }   
       catch (InterruptedException e) {e.printStackTrace();}   
       finally  
       {  
           lock.unlock();  
       }  
    }  
//more code ...  


現在のアカウント残高が不足している場合、スレッドがブロックされ、ロックが放棄され、他のスレッドがロックを取得し続ける機会があります.ロックの取得を待つスレッドはawitメソッドを呼び出すスレッドと本質的に異なる.スレッドがawitメソッドを呼び出すと、現在の条件の待機セットに入ります.ロックが使用可能な場合、スレッドはすぐにブロックを解除することはできません.他のスレッドがsignalAllメソッドを呼び出すまでブロック状態が続きます.この方法は、条件が待機セットに入るためのすべてのプロセスを通知するだけで、これらのプロセスを待機セットから除去し、ロックが使用可能になると、オブジェクトに再アクセスしようとし、awit法違反呼び出しから戻り、ロックを取得し、ブロックされた場所から実行を継続する.
Condition newCondition()    //                
void awit()         //               
void signalAll()        //                    
void signal()           //