Java多線程實現的三種方式


對於所有語言來說,多線程的編程是絕不可少的。同樣的Java語言也包含了多線程的開發。首先,我們先來了解一下Java語言的多線程實現方式。
 
一、Java 多線程實現方式
java中實現多線程的方式有三種,接下來我將會逐個進行介紹。
1.繼承Thread類
繼承Thread類是Java中比較常見,也是很基礎的一種實現Java多線程的方式。實現的方式也比較簡單,只要將需要實現多線程的Java類繼承java.lang.Thread類即可。
class MyThread extends Thread{
       
    private String name ;
 
    public MyThread(String name ){
        this . name = name ;
    }
 
    @Override
    public void run() {       
        Thread. currentThread ().setName( name ); 
         System.out.println("I am Thread :" +name);     
    }
 
}
如上代碼片段則是通過繼承Thread類實現多線程的方式之一。那么多線程該如何調用呢?請接着閱讀下邊的代碼。
public class threadLearn {
       
    public static void main(String[] args )  {
        //實例化繼承了Thread的類
        MyThread thread1 = new MyThread( "Thread1" );
        //通過從Thread類中所繼承的start()方法啟動線程;
         thread1 .start();     
 
    }
 
}
運行上邊的代碼,可以得到結果:
 

 

到這里,一個簡單的線程已經被啟動了。下邊我們接着介紹另外兩種多線程的實現方式。
 
2.實現Runable接口
接下來請看這樣一種場景,DogRobot是一個用來看門的Dog,現在需要多個Dog分別把守不同的位置,但DogRobot一定要繼承Robot父類,此時應該如何實現多線程呢?
在這種場景下,可以通過實現Runable接口的方式來實現一個類的多線程。
我們知道,在Java中,類的繼承是單一的,即一個子類僅可以繼承一個父類(一個兒子只有一個爹,符合自然規律),但可以實現多個接口。那么,通過Runable接口來實現多線程的好處自然不言而喻。
接下來看一下實現方式:
public class threadLearn {
   
    public static void main(String[] args )  {
        //實例化繼承了Thread的類
        MyThread thread1 = new MyThread( "Thread1" );
        //通過從Thread類中所繼承的start()方法啟動線程;
        thread1 .start();  
        
        /*
         * 實現Runnable的類,需要將其放在一個Thread實例對象中,
         * 由Thread實例對象進行線程的啟動及控制。
         */
        Thread threadDog = new Thread( new DogRobot( "kiki" ));
        threadDog .start();
    }
}
 
class DogRobot extends Robot implements Runnable{
   
    private String name ;
    public DogRobot(String name ){
        super ( name );
        this . name = name ;
    }
    @Override
    public void run() {     
        Thread. currentThread ().setName( name );
        System. out .println( "I am DogRobot :" + name );    
    }
}
class Robot {
       
        private String Name ;
       
        public Robot(String name )
       {
               this . Name = name ;
       }
       
}
 
 

 

下邊接着講解第三種多線程的實現方式。
 
 
 
3. 使用Executor框架實現多線程
在介紹Excutor實現多線程之前,我們先來學習另外一種多線程的實現方式,即繼承Callable接口實現多線程的方式。
首先我們來看一下Callable的接口:
可以看到,Callable的接口只有一個方法,那么我們在實現這個接口的時候也僅需要實現這個方法即可。
/*
 * Callable 接口實現多線程Demo
 */
class   MyCallable<V> implements Callable<V>
{
        @Override
        public V call() throws Exception {
               // TODO Auto-generated method stub
              System. out .println( "I am Callable thread : " +Thread. currentThread ().getName());
               return null ;
       }
       
}
 
如何調用這個線程,使其執行任務呢?那么,是需要通過FutureTask<V>這個類的實例去調度,我們首先來看一下FutureTask類的結構:
 
public class FutureTask<V> implements RunnableFuture<V>
這是FutureTask的實現方式,我們可以看到FutrueTask是實現了RunnableFuture的方法,那么RunnableFuture又是做什么的呢?我們接着跟進去看看結構:
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}
 
以上您所看到的,正是RunnableFuture接口的結構,我們可以看到,這個接口,是繼承了Runnable接口,和Future接口的一個接口,至於Future接口是什么,我們后續會講到。
如果您看到了Runnable接口,那么我想應該已經明了了,實現了Runnable接口的類,可以通過Thread實例對象來驅動,從而運行一個獨立的線程。這是我們前邊所講到的。
 
到這里,還有一個問題,FutureTask和Callable接口有什么關系呢?
那么我們接着看FutureTask的構造方法:
 
/**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param   callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask (Callable<V> callable ) {
        if ( callable == null )
            throw new NullPointerException();
        this . callable = callable ;
        this . state = NEW ;       // ensure visibility of callable
    }
 
可以看到,FutureTask的構造方法之一所需要的參數,就是Callable的實例對象。為什么說之一呢,因為還有一個構造方法,是通過Runnable接口實現的,如何實現的呢:
public FutureTask(Runnable runnable , V result ) {
        this . callable = Executors. callable ( runnable , result );
        this . state = NEW ;       // ensure visibility of callable
    }
其實我們繼續跟入進去可以發現,其實這個方法本質上還是生成了一個Callable的對象,最后賦予自己內部的this.callable;感興趣的同學可以自己試一試這種方式。
 
回到我們的話題,繼續實現調用:
    public static void main(String[] args )  {
 
        /*
         * 使用 Callable 來創建線程
         */
        Callable <Integer> aCallable = new MyCallable<Integer>();
       
        FutureTask<Integer> aTask = new FutureTask<Integer>( aCallable );
       
        Thread aThread = new Thread( aTask );
       
        aThread .start();
    }
 
我們運行一下程序,可以看到,運行的結果如下圖所示:
 

 

 
到這里,一個通過Callable實現的接口便是成功了。
 
那么這里會有一個問題,按照這樣的實現方式,那和Runnable接口有什么區別???
其實我們應該注意到了,Callable接口里的call方法,是一個有返回值的方法;
且FutureTask類的實現方式中,針對Runnable的實現方式,也是攜帶有一個參數result,由result和Runnable實例去合並成一個Callable的實例。
 
小本本拿出來划重點了。
 
實現了Callable接口的線程,是具有返回值的。而對於一些對線程要求有返回值的場景,是非常適用的
 
接下來,我們就看一下,如何獲通過Callable接口獲取線程的返回值。
 
獲取Callable接口的返回值,需要使用Future接口的實例化對象通過get的方式獲取出來。
 
 
Mark -- 2018 04 24 
今天接着學習多線程。昨天說到通過實現Callable接口實現多線程,今天我們來看如何通過Callable線程獲取到線程的返回值。
首先我們先來認識幾個接口及一個工廠類
 
ExecutorService 接口 
public interface ExecutorService extends Executor
ExecutorService接口是實現線程池經常使用的接口。通常我們使用的線程池、定時任務線程池都是他的實現類。
 
Executors工廠類
public class Executors
Executors是一個工廠類,通過調用工廠類的方法,可以返回各種線程池的實現類。比如我們接下來要用到的:
public static ExecutorService newFixedThreadPool( int nThreads ) {
        return new ThreadPoolExecutor( nThreads , nThreads ,
                                      0L, TimeUnit. MILLISECONDS ,
                                      new LinkedBlockingQueue<Runnable>());
    }
 
我們可以看到,返回值的類型是一個 ExecutorService 而實際上返回的返回值,則是一個 ThreadPoolExecutor ,我們接着跟進去看下ThreadPoolExecutor是什么:
public class ThreadPoolExecutor extends AbstractExecutorService
到這里,我們可以看到,ThreadPoolExecutor是一個繼承了 AbstractExecutorService 的實現類。
這個線程池類里定義了各種我們需要對線程池進行操作的方法。后續有時間我們再研究一下源碼。
 
有了上述的這些基本了解,我們則可以進行線程池的創建:
int taskSize = 5;        
ExecutorService   exPool = Executors. newFixedThreadPool ( taskSize );
如上邊這樣的代碼,我們就可以創建一個具有5個線程容量的線程池。
那么如何將Callabe接口添加到線程池中運行呢?
 
ExcutorService提供了一個方法供我們調用:
<T> Future<T> submit(Callable<T> task );
我們看到,這個submit方法也只是接口里定義的一個方法,那么他的實現方法是什么呢,我們代碼里跟一下:
 

 

可以看到,有很多個實現的方法,那么,我們這里調用的是哪一個呢?
很顯然,我們調用的是AbstractExecutorService中的實現方法。因為用來調用submit的實例對象實際上正是ThreadPoolExecutor,剛才我們也提到了,ThreadPoolExecutor是繼承了AbstractExcutorService的。
那么結果應該相當的明顯了吧。
 
    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task ) {
        if ( task == null ) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor( task );
        execute( ftask );
        return ftask ;
    }
看到這里是不是覺得有一絲絲的熟悉,正如我們第三小節所提到的 RunnableFuture ,從本質上,我們還是返回了一個RunnableFuture類型的實例。因此,我們可以通過該實例獲取到線程的返回值。
 
那么接下來,我們寫一個小demo來實踐一下,通過ExcutorService來加載Callable接口實現多線程,並且得到返回值。
首先,我們來寫一個Callable接口的實現類
/*
 * Callable 接口實現多線程Demo
 */
class   MyCallable implements Callable<Object>
{
        private int task_id ;
       
       MyCallable( int task_id )
       {
               this . task_id = task_id ;
       }
        @Override
        public Object call() throws Exception {
               // TODO Auto-generated method stub
              System. out .println( "I am Callable thread : " +Thread. currentThread ().getName());
              
              Random rd = new Random();
              
               int leng = rd .nextInt(9)+1;
               int sum = 0 ;
               for ( int i = 0 ; i <= leng ; i ++ )
              {
                     Thread. sleep (1000);
                      sum += i ;
              }
              
              System. out .println( "I am Callable thread : " +Thread. currentThread ().getName()
                           + "Thread name is : [" + this . task_id + "]"
                           + " I worked done at [" + new Date().getTime()+ "]"
                           + " Random Num = " + leng
                           );
              
               return "The task [" + this . task_id + "] get result :【 " + sum + " 】" ;
       }
       
}
這個線程會返回1-10以內的隨機數的自增合。我們來通過線程池調度一下:
 
        /*
         *使用 Excutor 來執行線程,並獲取返回值
         */
        int taskSize = 5;
       
        ExecutorService  exPool = Executors. newFixedThreadPool ( taskSize );
       
        //使用Future來獲取線程的結果
        List< Future > list = new ArrayList< Future >(); 
       
        for ( int i = 0; i < taskSize ; i ++) { 
            Callable c = new MyCallable( i ); 
            // 執行任務並獲取Future對象 
            Future f = exPool .submit( c );  
            list .add( f ); 
           } 
        exPool .shutdown();
        System. out .println( "The time we shutDown The Executor Pool : 【" + new Date().getTime()+ "】" );
       
        for ( Future f : list ) { 
            // 從Future對象上獲取任務的返回值,並輸出到控制台 
            try {
                           System. out .println( ">>>" + f .get().toString());
                     } catch (InterruptedException e ) {
                            // TODO Auto-generated catch block
                            e .printStackTrace();
                     } catch (ExecutionException e ) {
                            // TODO Auto-generated catch block
                            e .printStackTrace();
                     } 
        }
 
我們創建了一個List用來存放每個線程的執行結果。
shutdown()方法並不是終止線程池,而是在執行shutdown()方法之后,線程池則不會再接收新加入的線程。
 
我們運行一下 ,下邊的是運行的結果:
 
從這個結果上,我們可以看到,線程4抽到的隨機數是1,那么他執行的時間是最短的,只sleep 1S, 其他的線程2/3/1分別sleep了3/4/7S,線程5Sleep了9S,有意思的事情發生了。
我們這條長長的輸出實在線程執行完自己的計算之后輸出的,還沒有返回值,此時其他的幾個線程的輸出結果已經答應出來了,線程5在執行完畢之后,主線程才打印出了線程5的結果。
到這里,關於Java線程的創建就告一段落。隨后我們將會繼續深入學習Java線程以及線程池的知識。
 
 
 
 
 
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM