Java中的進程與線程(總結篇)

詳細文檔:java

Java中的進程與線程.rar 474KB 1/7/2017 6:21:15 PM

概述:編程

幾乎任何的操做系統都支持運行多個任務,一般一個任務就是一個程序,而一個程序就是一個進程。當一個進程運行時,內部可能包括多個順序執行流,每一個順序執行流就是一個線程。安全

進程與線程:多線程

進程是指處於運行過程當中的程序,而且具備必定的獨立功能。進程是系統進行資源分配和調度的一個單位。當程序進入內存運行時,即爲線程。併發

 

進程擁有如下三個特色:jvm

1:獨立性:進程是系統中獨立存在的實體,它能夠獨立擁有資源,每個進程都有本身獨立的地址空間,沒有進程自己的運行,用戶進程不能夠直接訪問其餘進程的地址空間。ide

2:動態性:進程和程序的區別在於進程是動態的,進程中有時間的概念,進程具備本身的生命週期和各類不一樣的狀態。測試

3:併發性:多個進程能夠在單個處理器上併發執行,互不影響。ui

 

併發性和並行性是不一樣的概念:並行是指同一時刻,多個命令在多個處理器上同時執行;併發是指在同一時刻,只有一條命令是在處理器上執行的,但多個進程命令被快速輪換執行,使得在宏觀上具備多個進程同時執行的效果。this

對於一個CPU而言:只能在某一時間點執行一個程序。

多進程的併發策略有:共用式的多任務操做策略(WIN3.1和Mac OS9),如今操做系統大多采用效率更高的搶佔式多任務操做策略(Windows NT、Windows 2000以及UNIX/Linux)等操做系統。

多線程擴展了多進程了概念,使得同一個進程能夠同時併發處理多個任務。
線程(Thread)被稱爲輕量級線程(LightWeight Process),線程是進程的執行單元。
 
線程是進程的組成部分,一個進程能夠擁有多個線程,而一個線程必須擁有一個父進程。線程能夠擁有本身的堆棧,本身的程序計數器和本身的局部變量,但不能擁有系統資源。它與父進程的其餘線程共享該進程的全部資源。
 
線程能夠完成必定任務,能夠和其它線程共享父進程的共享變量和部分環境,相互協做來完成任務。
線程是獨立運行的,其不知道進程中是否還有其餘線程存在。
線程的執行是搶佔式的,也就是說,當前執行的線程隨時可能被掛起,以便運行另外一個線程。
一個線程能夠建立或撤銷另外一個線程,一個進程中的多個線程能夠併發執行。
 
線程的建立和啓用:
java使用Thread類表明線程,全部的線程對象都必須是Thread或者其子類的實例,每一個線程的做用是完成必定任務,其實是就是執行一段程序流(一段順序執行的代碼)
繼承Thread類建立線類
1:定義Thread類的子類 並重寫該類的Run方法 該run方法的方法體就表明了線程須要完成的任務
2:建立Thread類的實例,即建立了線程對象
3:調用線程的start方法來啓動線程
 
例:
package Test;

public class FirstThread extends Thread{
    private int i;
@Override
public void run() {
    for(;i<10;i++)
    {
        System.out.println(getName()+"\t"+i);
    }
}
public static void main(String[] args)
{
    for (int i = 0; i <10; i++) {
      System.out.println(Thread.currentThread().getName()+"\t"+i);    
      if(i==5)
        {
            FirstThread f1=new FirstThread();
            FirstThread f2=new FirstThread();
            f1.start();
            f2.start();
        }
    }
    
}
}
View Code

 

 

Thread.currentThread():老是返回正在執行的線程對象
getName()返回當前正在執行的線程名
 
使用繼承子Thread類的子類來建立線程類時,多個線程沒法共享線程類的實例變量(好比上面的i)
 
 
使用Runnable接口建立線程類
1:定義Runnable接口的實現類,並重寫它的Run方法,run方法一樣是該線程的執行體!
2:建立Runnable實現類的實例,並將此實例做爲Thread的target建立一個Thread對象,該Thread對象纔是真正的線程對象!
3:調用start方法啓動該線程
Runnable對象僅做爲Thread對象的target,Runnable實現類裏包含的run()方法僅做爲線程執行體。而實際的線程的對象依舊是Thread實例,只是線程實例負責執行其target的run()方法
package Test;

public class SecondThread implements Runnable {
    private int i;
    @Override
    public void run() {
        for (; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20)
            {
                System.out.println(Thread.currentThread().getName()+"執行完畢");
            }
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==5)
            {
                SecondThread s1=new SecondThread();
                Thread t1=new Thread(s1,"線程1");
                Thread t2=new Thread(s1,"線程2");
                t1.start();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                t2.start();
            }
        }
    }

}
View Code  

 

因爲線程的不穩定性,可能同時出現線程1和線程2

 

採用Ruunable接口的方式建立多個線程能夠共享線程類的實例變量,這是由於在這種方式下,程序建立的Runnable對象只是線程的target,而多個線程能夠共享一個target,因此多個線程能夠共享一個實例變量!

使用callable和future建立線程

經過Runnable實現多線程其實就是將run包裝成線程的執行體,可是目前java沒法將任意方法包裝成線程執行體

從Java5開始,Java提供 Callable接口,Callable接口提供了一個call()方法能夠做爲線程執行體,看起來和Runnable很像,但call()方法更強大——call()方法能夠有返回值、call()方法能夠拋出異常

Java5提供了Future接口來表明Callable接口的call()方法的返回值,並未Future接口提供了一個FutureTask實現類,該實現類實現類Future接口,也實現了Runnable接口——能夠做爲Thread的target。

 

使用該方法建立有返回值的線程的步驟以下:

1:建立Callable接口的實現類,並實現call方法,該call方法會成爲線程執行體,且call方法具備返回值,在建立callable接口的實現類!

2:使用FutrueTask類來包裝Callable對象,該FutrueTask封裝類Callable的call方法的返回值

3:使用FutrueTask對象做爲Thread的target建立並啓動新線程!

4:使用FutrueTask的get方法獲取執行結束後的返回值

package Test;

import java.util.concurrent.Callable;

public class target implements Callable<Integer> {
    int i=0;
    @Override
    public Integer call() throws Exception {
        for (; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+""+i);
        }
        return i;
    }

}
package Test;

import java.util.concurrent.FutureTask;

public class ThridThread {

    public static void main(String[] args) {
        target t1=new target();
        FutureTask<Integer> ft=new FutureTask<Integer>(t1);
        Thread t2=new Thread(ft,"新線程");
        t2.start();
        try {
            System.out.println(ft.get());
        } catch (Exception e) {
            // TODO: handle exception
        }

    }

}
View Code

 

採起Runnable、Callable的優點在於——線程類只是實現了Runnable或Callable接口,還能夠繼承其它類;在這種方法下,多個線程能夠共享一個target對象,所以很是適合多個相同線程處理同一份資源的狀況,從而將CPU、代碼和數據分開,形參清晰的模型,體現了面對對象的編程思想。劣勢在於編程複雜度略高。

線程的生命週期:

當線程被建立並被啓動時,它既不是一啓動就進入了執行狀態,在線程的生命週期中,它要通過new(新建),就緒(Runnable),運行(Running),阻塞(Blocked),dead(死亡)。

當線程啓動以後,它不可能一直霸佔着cpu獨自運行,全部cpu須要在多條線程輪流切換,因而線程就也會屢次在運行.就緒之間切換。

新建和就緒狀態

當程序使用new關鍵字建立了一個線程時,該線程就處於新建狀態,此時它和其它java對象同樣,僅有虛擬機分配內存,並初始化成員變量的值。此時的線程對象並無表現出線程的任何動態特徵,程序也不會執行線程的線程執行體。

 

當線程對象調用了start()方法後,該線程就處於就緒狀態,java虛擬機會爲其建立方法調用棧和程序計數器,處於該狀態的線程並無開始執行,只是代表該線程能夠運行了,至於該線程什麼時候運行,取決於JVM的調度。

啓動線程要調用start方法,而不是run方法,永遠不要調用線程的run方法,若是調用run方法,系統會把線程對象看成普通的對象,會吧線程的執行體看成普通方法來調用!

 

在調用了run方法以後,該線程就不在處於新建狀態,不要再調用該線程的start方法!

 

java中只能對處於新建狀態的線程使用start方法,不然將會引起IllegalThreadStateException異常!

 

若是但願調用子線程的start()方法後子線程當即開始執行,可使用Thread.sleep(1)來讓當前運行的線程(主線程)睡眠一毫秒,這樣CPU就會當即啓動另外一個處於就緒狀態的線程,須要注意的是,使用Thread.sleep()方法須要聲明InterruptedException異常!

 

運行和阻塞狀態:

當發生以下的幾種狀況時,將會進入阻塞狀態:

當線程調用sleep方法主動放棄所佔用的處理器資源

線程調用了一個阻塞時的IO方法,在該方法返回以前,線程會被阻塞

線程試圖得到一個同步監視器,但該同步監視器正被其餘線程鎖持有

線程正在等待某個通知(notify)

程序調用了線程的suspend方法將該線程掛起

 

當以上幾個狀況,當發生以下的狀況將會從新進入就緒狀態

調用sleep()方法過了指定時間
 
線程調用的阻塞時IO方法依舊返回
 
線程成功地得到了試圖得到的同步監視器
 
如今正在等待某個通知,而其它線程發出一個通知
 
處於掛起狀態的線程被調用了resume()方法

 

線程從阻塞狀態只能進入就緒狀態,沒法直接進入運行狀態。就緒和運行狀態之間的轉換一般不受程序控制,而是系統線程的調度決定的。

調用yield()方法可讓處於運行時的線程轉入就緒狀態。

 

線程死亡:

線程會以如下三種方式結束,結束後處於死亡狀態

run或call方法執行完成,程序結束

線程拋出一個未捕獲的Exception或者Error

直接調用該線程的stop方法來結束線程

 

 

當主線程結束時,其它線程不受任何影響,並不會隨之結束。一旦子線程啓動起來後,他就會擁有和主線程相同的地位,它不會受主線程影響。

 

爲了測試某個線程是否死亡,能夠調用該線程的isAlive方法,當線程處於就緒,運行,阻塞三種狀態時,將返回true;當線程處於新建,死亡兩種狀態時返回爲false。

不要試圖對一個已經死亡的線程調用start方法讓它從新啓動,死亡後的線程沒法做爲線程使用。

 

若是處於非新建狀態的線程使用start方法,就會引起IllegalThreadStateException異常。

 

控制線程:

join線程:

Thread提供了讓一個線程等待另外一個線程完成的方法--join方法,當在某個程序執行流中調用其餘線程的join方法,調用線程將被阻塞,直到被join方法加入的join線程執行完畢爲止。

 

join方法一般由使用線程的程序調用,以將大問題劃爲許多小問題,每一個小問題分配一個線程,當全部的小問題都被處理以後,再調用主線程進行下一步操做!

 

package Test1;

public class JoinThread extends Thread{

    public JoinThread(String name)
    {
        super(name);
    }
    @Override
    public void run()
    {
        for (int i = 0; i <10; i++) {
            System.out.println(getName()+"\t"+i);
        }
    }
}
package Test1;

public class Test {
public static void main(String[] args) throws InterruptedException {
    new JoinThread("新線程").start();
    for (int i = 0; i <10; i++) {
        if(i==5)
        {
            JoinThread j1=new JoinThread("被join的線程");
            j1.start();
            j1.join();
        }
        System.out.println(Thread.currentThread().getName()+" "+i);
    }
}
}
View Code

 

 

 

 

在被join的線程執行前,兩個線程交替執行,而主線程處於等待狀態,直到被join的線程執行完畢,主線程繼續執行!

 

後臺線程:

有一種線程,是在後臺運行的,其任務是爲其餘線程提供服務,這種線程稱之爲後臺線程(Daemon Thread),又稱之爲守護線程。jvm的垃圾回收器就是典型的後臺進程。

 

當前臺線程所有死亡,後臺線程會自動死亡

 

調用Thread的setDaemon(ture)方法能夠將指定線程設置成爲後臺線程。

 

當整個虛擬機只剩下後臺線程時,程序就沒有運行的必要了,全部虛擬機將退出

 

Thread類還提供了一個isDaemon方法,用於指定該線程是不是後臺線程!

 

前臺建立的線程默認爲前臺線程,然後臺建立的線程默認爲後臺線程。

 

前臺線程死亡時,jvm會通知後臺線程死亡,但它從接受指令到作出響應須要一段時間 此外,若是要將某個線程設置爲後臺線程,必需要在該線程啓動以前設置

,也就是setDaemon(true)必須在start方法以前調用,不然會引起IllegalThreadStateException異常。

線程睡眠:sleep

 

當前線程調用sleep方法進入阻塞狀態時,在其睡眠時間內,該線程不會得到執行的機會

 

即使系統中沒有其它可執行的線程,處於sleep的線程也不會執行,所以sleep方法經常使用於暫停程序的執行!

 

線程讓步:

yield會讓該線程暫停,可是它不會阻塞線程,其只是將線程轉入就緒狀態,也就是說,yield方法只是讓當前線程暫停一下,讓系統的線程調度器從新調度一次,徹底可能出現這種狀況,--某個線程調用了yield後,線程調度器又將其調用出來執行

在多CPU並行的環境下,yield功能有時並不明顯

sleep()方法和yield方法的區別:

sleep方法暫停當前線程後會給其它線程執行機會,不會理會其它線程的優先級,但yield方法以後給優先級相同,或優先級更高的線程執行機會。

sleep方法會將線程轉入阻塞狀態,直到通過阻塞時間纔會轉爲就緒狀態,而yield方法不會轉入阻塞狀態,只是強制將當前線程轉入就緒狀態

sleep方法聲明拋出了InterruptedException異常,全部調用sleep方法就要捕獲此異常,而yield方法則沒有

sleep方法比yield方法有更好的執行!

改變線程的優先級:

每一個線程都有必定的優先級,優先級更高的線程將會有更多的執行機會

每一個線程默認的優先級都與建立它的父進程的優先級相同,默認狀況下,main進程具備普通優先級

Thread類提供setPriority(int newPriority)和getPriority()方法來設置和返回線程的優先級其中setPriority參數是int類型,範圍0到10之間

Thread類有三個靜態常量:MAX_PRIORITY :10  MIN_PRIORITY :1 NORM_PRIORITY:5

線程同步:

同步代碼塊:

 

synchronize(obj){

}

 

obj:同步監視器,含義:線程開始執行同步代碼塊時,必須先得到對同步監聽器的鎖定

任什麼時候刻只能有一個線程能夠得到對同步監視器的鎖定,當同步代碼塊完成執行後,該線程會釋放對該同步監視器的鎖定。
 
雖然Java程序容許使用任何對象做爲同步監聽器,但一般推薦使用可能被併發訪問的共享資源充當同步監視器。
 
同步方法:
與同步代碼塊,Java的多線程安全支持還提供了同步方法,同步方法就是使用某個synchronized關鍵字修飾某個方法,則該方法被稱爲同步方法。
對於被synchroize修飾的方法(非static方法而言),無需顯示指定同步監視器,同步方法的同步監視器是this,也就是調用該方法調用的對象
synchronized關鍵字能夠修飾方法,能夠修飾代碼塊,但不能修飾構造器、成員變量。
線程安全的類具備以下特徵:
該類的對象能夠被多個線程安全地訪問
每一個線程調用該對象的任一方法以後都將獲得正確的結果
每一個線程調用該對象的任一方法以後,該對象狀態依然保持合理狀態
 
package Test2;

public class Account {
   private String AccountNo;
   private double balance;
   public Account(){  
   }
   public Account(String accountNo, double balance) {
    AccountNo = accountNo;
    this.balance = balance;
   }
public String getAccountNo() {
    return AccountNo;
}
public double getBalance() {
    return balance;
}
public void setAccountNo(String accountNo) {
    AccountNo = accountNo;
}
public void setBalance(double balance) {
    this.balance = balance;
}
   public synchronized void draw(double drawcount)
   {
       if(balance>=drawcount)
       {
           System.out.println(Thread.currentThread().getName()+"取款成功,取出"+drawcount+"元");
           try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
           balance-=drawcount;
           System.out.println("餘額:"+balance);
       }
       else
       {
           System.out.println(Thread.currentThread().getName()+" "+"餘額不足");
       }
   }
   public int hashCode()
   {
       return AccountNo.hashCode();
   }
   public boolean equals(Object obj)
   {
       if(this==obj)
           return true;
       if(obj!=null&&obj.getClass()==Account.class)
       {
           Account a=(Account)obj;
           return a.getAccountNo().equals(AccountNo);
       }
       return false;
   }

}
案例一
package Test2;

public class Test extends Thread {
    private Account account;
    private double drawAccount;
    /**
     * @param account
     * @param drawAccount
     */
    public Test(String name,Account account, double drawAccount) {
        super(name);
        this.account = account;
        this.drawAccount = drawAccount;
    }
    @Override
    public void run() {
        account.draw(drawAccount);
    }
    public static void main(String[] args) {
        Account a=new Account("3242332",1000);
        Test t1=new Test("甲", a, 600);
        Test t2=new Test("乙", a, 600);
        t1.start();
        t2.start();
    }
    
}
View Code
package Test2;

public class Test extends Thread {
    private Account account;
    private double drawAccount;
    /**
     * @param account
     * @param drawAccount
     */
    public Test(String name,Account account, double drawAccount) {
        super(name);
        this.account = account;
        this.drawAccount = drawAccount;
    }
    @Override
    public void run() {
        account.draw(drawAccount);
    }
    public static void main(String[] args) {
        Account a=new Account("3242332",1000);
        Test t1=new Test("甲", a, 600);
        Test t2=new Test("乙", a, 600);
        t1.start();
        t2.start();
    }
    
}
View Code

 

可變類的線程安全是以下降運行效率爲代價的,爲減小線程安全鎖帶來的負面影響,可採用如下策略:
不要對線程安全類中的全部方法都同步,只對那些會改變競爭資源(競爭資源也就是共享資源)的方法進行同步。
若是可變類有單線程和多線程兩種運行環境,那麼要爲該可變類提供兩種版本(線程安全版和線程不安全版)
 
StringBuffer和StringBuilder就是這種狀況,在單線程時應使用StringBuilder,多線程時使用StringBuffer。

釋放同步監視器的鎖定

線程會在如下幾種狀況下釋放對同步監聽器的鎖定:
當前線程的同步方法、同步代碼塊執行結束,當前線程釋放了同步監聽器。
當前線程在同步代碼塊、同步方法中遇到了break、continue,終止了該代碼塊、方法的運行,當前線程釋放了同步監聽器。
當前線程在同步代碼塊、同步方法中遇到了未處理的error、exception,致使該代碼塊、方法意外結束,當前線程釋放了同步監聽器。
當前線程執行同步代碼塊或同步方法時,程序執行了同步監聽器對象的wait()方法,則當前線程暫停,並釋放同步監聽器。
 
在以下所示的狀況下,線程不會釋放同步監聽器:
線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法來暫停當前線程的執行,當前線程不會釋放同步監視器。
線程執行同步代碼塊時,其它線程調用了該線程的suspend()方法將該方法掛起,該線程不會釋放同步監聽器。(全部程序應該避免使用suspend和resume來操控線程)
 
 

同步鎖(Lock)

從Java5開始,Java提供了一種功能更強大的線程同步機制——經過顯式定義同步鎖對象來實現同步,在這種機制下,同步鎖有Lock對象充當。
 
Lock提供了比synchronized方法和synchronized代碼塊更普遍的鎖定操做,Lock容許實現更靈活的結構,能夠具備很大的差異的屬性,並支持多個相關的Condition對象。
 
鎖提供了對共享資源的獨佔訪問,每次只有一個線程對Lock對象加鎖,線程開始訪問共享資源以前應先得到Lock對象。
 
某些鎖可能容許對共享資源併發訪問,如ReadWriteLock(讀寫鎖),Lock、ReadWriteLock是JAVA5提供的兩個根接口,並未Lock提供了ReetrantLock(可重入鎖)的實現類,爲ReadWriteLock提供了ReentrantReadWriteLock實現類。
 
JAVA8新增了新型的StrampedLock類,在大多數場景下它能夠替代傳統的ReentrantReadWriteLock。ReentrantReadWriteLock提供了三種鎖模式——Writing,ReadingOptimistic,Reading。
 
在實現線程安全的控制中,比較經常使用的ReentrantLock。使用該對象能夠顯式的加鎖、釋放鎖。
 
使用ReentrantLock對象進行同步,加鎖和釋放鎖出如今不一樣的做用範圍時,一般建議使用finally塊確保在必要時釋放鎖。
 
 
ReentrantLock鎖具備可重入性,也就是說一個線程能夠對已被加鎖的ReentrantLock再次加鎖,ReentrantLock對象會維持一個計數器來追蹤lock()方法的嵌套調用,線程在每次調用lock()加鎖後,必須顯式的地調用unlock()來釋放鎖,因此一段被鎖的代碼能夠調用另外一個被相同鎖保護的方法。
 
 
我的理解,所謂鎖,就是指在鎖的範圍內必須一次性執行,不能中途掛起並執行其它線程。
 

死鎖

當兩線程相互等待對方釋放同步監視器是就會發生死鎖,JAVA虛擬機沒有監測、處理死鎖的措施,因此必定要避免死鎖的出現。
 
一旦出現死鎖,整個程序不會出現任何異常,也不會給出任何提示,只是全部線程處於阻塞狀態,沒法繼續。
 
可是死鎖很容易發生,尤爲是系統中出現多個同步監聽器的狀況下。
 
因爲Thread類的suspend()方法也很容易致使死鎖,全部JAVA再也不推薦使用該方法來暫停線程的執行。
相關文章
相關標籤/搜索