java實現/創建線程的幾種方式小結

進程與線程

進程可以簡單理解成一個可執行程序例如.exe,在Windows中的任務管理器中可以查看每一個進程,進程是一次程序的執行,是程序在數據集合上運行的過程,是系統資源調度的一個單位。進程主要負責向操作系統申請資源。然而一個進程中,多個線程可以共享進程中相同的內存或文件資源。線程就是一個進程一個程序要完成所依賴的子任務,這些子任務便可以看作是一個線程。

第一種方式繼承Thread類

從java源碼可以看出Thread類本質上實現瞭Runnable接口的實例類,代表瞭線程的一個線程的實例,啟動的線程唯一辦法就是通過Thread類調用start()方法,start()方法是需要本地操作系統的支持,它將啟動一個新的線程,並且執行run()方法。

繼承Thread類實現線程代碼如下

創建一個Thread類,對象直接調用run方法會出現什麼問題?

package cn.thread.線程;

public class MyThread extends Thread{
    public MyThread(String name){
        super(null,null,name);
    }
    int piao =10;
    @Override
    public void run() {
        while(piao>0){
            System.out.println(Thread.currentThread().getName()+"......"+piao--);
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread("x");
        mt.run();
    }
}

結果:

可以發現是主線程執行瞭run方法,並不是用戶線程執行的run方法,此時可以得出用戶線程並沒有啟動,所以並不會執行run裡面的方法,且執行完run方法便結束線程。

第二種創建線程的方法,實現Runnable接口

相比繼承Thread類而言,實現接口的可擴展性得到瞭提升,Runnable接口也必須要封裝到Thread類裡面,才可以調用start方法,啟動線程。

實現代碼

package cn.thread.線程;

public class MyRunnable implements Runnable{

    int piao = 10;
    @Override
    public void run() {
        while(piao>0){
            System.out.println(Thread.currentThread().getName()+"-----"+piao--);
        }
    }

    public static void main(String[] args) {
        Runnable r =new MyRunnable();
        Thread t =new Thread(r);
        t.start();
    }

}

結果

第三種創建線程的方法實現Callable接口

Callable接口使用方法和Runnable接口的方法類似不同的一點是Callable接口具有返回值,返回結果並且可能拋出異常的任務。實現者定義瞭一個不帶任何參數的叫做 call 的方法。 Callable 接口類似於 Runnable,兩者都是為那些其實例可能被另一個線程執行的類設計的。但是 Runnable 不會返回結果,並且無法拋出經過檢查的異常。

實現代碼

對下列代碼進行分析

首先callable是接口不能直接創建對象,也不能創建線程。並且要實現call方法類似run方法的功能,call方法有返回值,會計算結果,如果無法計算結果,則拋出一個異常。

執行callable任務之後,可以獲得一個future的對象,future基本上是主線程可以跟蹤進度以及獲取其他線程結果的一種方式。在這裡Test1()方法主要利用線程池和future的方法,去啟動實現Callable接口的線程,具有返回值。而Test2()主要是采用FutureTask類去實現創建一個實現callable接口的線程,futuretask實現瞭future接口。

package com.openlab.test;

import java.util.Random;
import java.util.concurrent.Callable;

public class CallableTest implements Callable{

	@Override
	public Object call() throws Exception {
		
		Random generator = new Random();
		
		Integer randomNumber = generator.nextInt(5);
		
		Thread.sleep(randomNumber*1000);
		
		return randomNumber;
	}

}

綜合練習代碼:

package cn.thread.線程;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

public class CallableTest implements Callable<Object> {//不能直接創建線程
    int taskNum;
    public CallableTest(int taskNum){
        this.taskNum = taskNum;

    }
    @Override
    public Object call() throws Exception {

        System.out.println(">>>"+taskNum+"任務啟動");
        Date dataTemp = new Date();
        Thread.sleep(1000);
        Date dataTemp2 = new Date();
        long time  = dataTemp2.getTime() - dataTemp.getTime();
        System.out.println(">>>>"+taskNum+"任務終止");
        return taskNum+"任務返回運行結果"+time;
//        Random generator = new Random();
//        Integer randomNumber = generator.nextInt(5);
//        Thread.sleep(randomNumber*1000);
//        return  randomNumber;
    }
    /*test1方法采用Executors的靜態方法newFixedThreadPool(taskSize) 創建一個可重用固定線程集合的
    線程池,以共享的無界隊列方式來運行這些線程,獲取線程池。ExecutorService的submit方法提交一個
    callable實例,得到一個future對象,最終將future對象存儲在list數組中,加入線程池的過程中就代表
    著線程已經開始執行,相當於一個線程池代理過程,就不需要采用start方法啟動線程。最後對future進行
    打印輸出。切記一定要關閉線程池!*/
     static void test1() throws ExecutionException, InterruptedException {
         System.out.println("程序開始");
         Date data1 = new Date();
         int taskSize = 5;
        //構建線程池對象
         ExecutorService pool = Executors.newFixedThreadPool(taskSize);
         List<Future> list =new ArrayList<Future>();
         for(int i=0;i<taskSize;i++){

             Callable c = new CallableTest(i);
             Future f = pool.submit(c);
             list.add(pool.submit(c));

         }
         //關閉線程池
         pool.shutdown();
         for(Future f:list){
             System.out.println(">>>"+f.get().toString());
         }
         Date date2 = new Date();
         System.out.println("程序運行結束-----"+(date2.getTime()-data1.getTime())+"毫秒");
     }
     /*test2方法主要是采用futuretask類,可以直接把callable作為參數來申明futuretask對象,
     這裡相當於把線程池換成瞭futuretask數組,因為test1線程池可以對callable進行封裝,
     在這裡可以直接采用futuretask就行封裝,在加上futuretask又實現瞭runnable接口,
     所以可以直接創建線程采用start的方式進行啟動線程。*/
     static  void test2() throws ExecutionException, InterruptedException {
         System.out.println("----程序開始-----");
         Date date1 =new Date();
         int taskSize = 5;
         FutureTask[] randNumber = new FutureTask[taskSize];
         List<Future> list =new ArrayList<Future>();
         for(int i=0;i<taskSize;i++){
             Callable c = new CallableTest(i);
             randNumber[i] = new FutureTask(c);
             Thread t = new Thread(randNumber[i]);
             t.start();

         }
         for(Future f:randNumber){
             System.out.println(">>>"+f.get().toString());
         }
         Date date2 = new Date();
         System.out.println("程序運行結束-----"+(date2.getTime()-date1.getTime())+"毫秒");

     }
    public static void main(String[] args) throws Exception {
//        CallableTest c = new CallableTest();
//        Integer i = (Integer) c.call();
        test1();
        test2();

    }
}

執行結果

第四種實現線程的方法,基於線程池

其實在第三種的方法中就提到瞭兩種實現方法,一種線程池+future,另一種futuretask的方法。線程和數據庫連接這些資源都是非常寶貴的資源。那麼每次需要的時候創建,不需要的時候銷毀,是非常浪費資源的。那麼我們就可以使用緩存的策略,也就是使用線程池。

// 創建線程池
 ExecutorService threadPool = Executors.newFixedThreadPool(10);
 while(true) {
 threadPool.execute(new Runnable() { // 提交多個線程任務,並執行
 @Override
 public void run() {
 System.out.println(Thread.currentThread().getName() + " is running ..");
 try {
 Thread.sleep(3000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 });
 } }

總結

理論上實現線程的方法還有一些,本文所提及到的,基本都是一些創建線程常用的方法。希望本文對大傢在學習線程的過程中有所幫助。

推薦閱讀: