談談java線程基礎

1. 操作系統(tǒng)中線程和進程的概念

  • 現(xiàn)在的操作系統(tǒng)是多任務操作系統(tǒng)。多線程是實現(xiàn)多任務的一種方式。
  • 進程是指一個內(nèi)存中運行的應用程序,每個進程都有自己獨立的一塊內(nèi)存空間,一個進程中可以啟動多個線程。比如在windows系統(tǒng)中,一個運行的exe就是一個進程。
  • 線程是指進程中的一個執(zhí)行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬于某個進程,進程中的多個線程共享進程的內(nèi)存。
  • 在java中要想實現(xiàn)多線程,有兩種手段,一種是繼承Thread類,另一種是實現(xiàn)Runable接口(其實準確來講,應該有三中的,還有一種是實現(xiàn)Callable接口,并與Future、線程池結合使用)

2. java線程的實現(xiàn)形式

這里繼承Thread類的方式是比較常用的一種,如果說你只是想起一條線程。沒有什么其他特殊的要求,那么可以使用Thread

3.繼承Thread類

/** 
 * 測試擴展Thread類實現(xiàn)的多線程程序 
 */  
public class TestThread extends Thread {  
    public TestThread(String name){  
       super(name);  
    }  
    @Override  
    public void run() {  
       for(int i=0;i<5;i++){  
           for(long k=0;k<100000000;k++);  
           System.out.println(this.getName()+":"+i);  
       }  
    }  
    public static void main(String[] args){  
       Thread t1=new TestThread("李白");  
       Thread t2=new TestThread("張飛");  
       t1.start();  
       t2.start();        
    }  
}  

執(zhí)行結果:
張飛:0
李白:0
張飛:1
李白:1
張飛:2
李白:2
張飛:3
張飛:4
李白:3
李白:4

4.實現(xiàn)Runnable接口

public class RunnableImpl implements Runnable{  
    private String name;  
    public RunnableImpl(String name) {  
       this.name = name;  
    }  
    @Override  
    public void run() {  
       for (int i = 0; i < 5; i++) {  
           for(long k=0;k<100000000;k++);  
           System.out.println(name+":"+i);  
       }       
    }  
}  
   
/** 
 * 測試Runnable類實現(xiàn)的多線程程序 
 */  
class TestRunnable {  
   
    public static void main(String[] args) {  
       RunnableImpl ri1=new RunnableImpl("李白");  
       RunnableImpl ri2=new RunnableImpl("張飛");  
       Thread t1=new Thread(ri1);  
       Thread t2=new Thread(ri2);  
       t1.start();  
       t2.start();  
    }  
}  

執(zhí)行結果:
李白:0
張飛:0
張飛:1
李白:1
張飛:2
李白:2
張飛:3
李白:3
張飛:4
李白:4

說明: Thread2類通過實現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個約定。所有的多線程代碼都在run方法里面。Thread類實際上也是實現(xiàn)了Runnable接口的類。 在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然后調(diào)用Thread對象的start()方法來運行多線程代碼。 實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是擴展Thread類還是實現(xiàn)Runnable接口來實現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。

5.使用Callable和Future接口創(chuàng)建線程

具體是創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)clall()方法。并使用FutureTask類來包裝Callable實現(xiàn)類的對象,且以此FutureTask對象作為Thread對象的target來創(chuàng)建線程。

public class ThreadTest {
 
     public static void main(String[] args) {
 
         Callable<Integer> myCallable = new MyCallable();    // 創(chuàng)建MyCallable對象
         FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象
 
        for (int i = 0; i < 100; i++) {
             System.out.println(Thread.currentThread().getName() + " " + i);
             if (i == 30) {
                 Thread thread = new Thread(ft);   //FutureTask對象作為Thread對象的target創(chuàng)建新的線程
                 thread.start();                      //線程進入到就緒狀態(tài)
             }
         }
 
         System.out.println("主線程for循環(huán)執(zhí)行完畢..");
         
         try {
             int sum = ft.get();            //取得新創(chuàng)建的新線程中的call()方法返回的結果
             System.out.println("sum = " + sum);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }
 }
 
 
 class MyCallable implements Callable<Integer> {
   private int i = 0;
 
     // 與run()方法不同的是,call()方法具有返回值
     @Override
     public Integer call() {
       int sum = 0;
         for (; i < 100; i++) {
             System.out.println(Thread.currentThread().getName() + " " + i);
             sum += i;
         }
         return sum;
 }

執(zhí)行結果:
main 0
main 1
main 2
main 3
...省略
main 99
主線程for循環(huán)執(zhí)行完畢..
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
...省略
Thread-0 99
sum = 4950

首先,我們發(fā)現(xiàn),在實現(xiàn)Callable接口中,此時不再是run()方法了,而是call()方法,此call()方法作為線程執(zhí)行體,同時還具有返回值!在創(chuàng)建新的線程時,是通過FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那么看下FutureTask類的定義:

public class FutureTask<V> implements RunnableFuture<V> {
         //....
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
         void run();
 }
  • 于是,我們發(fā)現(xiàn)FutureTask類實際上是同時實現(xiàn)了Runnable和Future接口,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新創(chuàng)建線程中的call()方法的返回值。
  • 執(zhí)行下此程序,我們發(fā)現(xiàn)sum = 4950永遠都是最后輸出的。而“主線程for循環(huán)執(zhí)行完畢..”則很可能是在子線程循環(huán)中間輸出。由CPU的線程調(diào)度機制,我們知道,“主線程for循環(huán)執(zhí)行完畢..”的輸出時機是沒有任何問題的,那么為什么sum =4950會永遠最后輸出呢?
  • 原因在于通過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執(zhí)行完畢,ft.get()方法會一直阻塞,直到call()方法執(zhí)行完畢才能取到返回值。
  • main方法其實也是一個線程。在java中所以的線程都是同時啟動的,至于什么時候,哪個先執(zhí)行,完全看誰先得到CPU的資源。
  • 在java中,每次程序運行至少啟動2個線程。一個是main線程,一個是垃圾收集線程。因為每當使用java命令執(zhí)行一個類的時候,實際上都會啟動一個JVM,每一個jVM實習在就是在操作系統(tǒng)中啟動了一個進程。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容