java多線程-基礎

1. 線程及進程

進程:進程是系統中正在運行的一個程序,程序一旦運行就是進程。
進程能夠當作程序執行的一個實例。進程是系統資源分配的獨立實體,每一個進程都擁有獨立的地址空間。一個進程沒法訪問另外一個進程的變量和數據結構,若是想讓一個進程訪問另外一個進程的資源,須要使用進程間通訊。進程是系統進行資源分配和調度的一個獨立單位。
線程:線程能夠理解爲進程中獨立運行的子任務。java

2. 多線程中經常使用方法

2.1 建立線程

經過繼承Thread類:
繼承Thread方法,重寫Thread的run()方法安全

public class MyThread extends Thread{
 
	 @Override
	public void run() {
	    doSomething();
	}
 
	private void doSomething() {
            System.out.println("我是一個線程中的方法");
	}
}

public class NewThread {
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		myThread.start();//開啓一個線程方法
	}
}

複製代碼

Thread類中的start()方法通知「線程規劃器」此線程已經準備就緒,等待調用線程對象run方法。若是調用myThread.run()就不是異步執行,而是同步,那麼此線程對象並不交給「線程規劃器」來進行處理,而是由main主線程來調用run()方法。
實現Runable接口bash

public class RunnableThread implements Runnable{
 
	@Override
	public void run() {
		doSomeThing();
	}
	private void doSomeThing() {
		System.out.println("我是一個線程方法");
	}
}
複製代碼

實現Runable接口類中沒有start()方法,須要用Thread構造個方法開啓線程。數據結構

public class NewThread {
	public static void main(String[] args) {
		Runnable runnable=new RunnableThread();
		Thread thread=new Thread(runnable);
		thread.start();//開啓一個線程方法
	}
}
複製代碼

實現Callable接口和Future建立線程
首先建立Callable接口的實現類CallableThread,實現call()方法,而且有返回值。Callable接口是一個帶泛型的接口,泛型的類型就是線程返回值的類型。實現Callable接口中的call()方法,方法的返回類型與泛型的類型相同。多線程

public class CallableThread implements Callable<String>{
 
	@Override
	public String call() throws Exception {
		doSomeThing();
		return "須要返回的值";
	}
 
	private void doSomeThing() {
		System.out.println("我是線程中的方法");
	}
}
複製代碼

Callable不能直接獲取返回值,須要用FutureTask在外部封裝一下再獲取返回值。併發

public class NewThread {
	public static void main(String[] args) {
		Callable<String> callable=new CallableThread();
		FutureTask<String> futureTask=new FutureTask<String>(callable);
		Thread thread=new Thread(futureTask);
		thread.start();//開啓一個線程方法
	       //如下的方法可與上邊的線程併發執行
		doSomething();
		try {
			futureTask.get();//獲取線程返回值
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}
 
	private static void doSomething() {
	}
}
複製代碼

三種建立方式的優缺點:異步

  1. 繼承Thread顯然有個很大的缺點,java是單繼承了,若是繼承了Thread那麼就沒法繼承其餘類。可是繼承Thread編寫簡單,實現方便。
  2. 實現Runnable接口和Callable接口。大體同樣,區別就是Callable接口的實現能夠有返回值,且能夠拋出顯示異常。其他大體同樣。 他們的優點是實現接口,那麼實現類能夠有其餘父類,避免的Thread的問題,其次能夠用一個實現了該接口的對象來創 建多個線程,從而方便一些基本的資源共享,由於是同一個對象。

2.2 currentThread()方法

currentThread()方法可返回代碼段正在被那個線程調用的信息。socket

public class MyRun extends Thread {

    public MyRun(){
        System.out.println("構造 thread :" + Thread.currentThread().getName());
        System.out.println("構造 thread :" + this.getName());
    }
    
    @Override
    public void run() {
        System.out.println("Thread.currentThread().getName(): " + Thread.currentThread().getName());
        System.out.println("this.getname:"+ this.getName());
    }
}
複製代碼
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        myRun.setName("aaaa");
        myRun.start();
    }
}
複製代碼

運行結果ide

構造 thread :main
構造 thread :Thread-0
Thread.currentThread().getName(): aaaa
this.getnam:eaaaa
複製代碼

2.3 isAlive()方法

isAlive()方法用於判斷當前線程是否處於活動狀態。活動狀態時線程已經啓動還沒有終止。線程處於正在運行或者準備開始運行的狀態就認爲線程是存活的。函數

2.4 sleep()方法

sleep()方法是在指定的毫秒數內讓當前「正在執行的線程」休眠(暫時執行)。這個正在執行的線程是指this.currentThread()返回的線程。

2.5 getId()方法

getId()方法能夠取得線程的惟一標識。

2.6 判斷線程是否中止

Thread.java類中提供了兩種方法來判斷線程是否中止。

  1. this.interrupted():測試當前線程是否已經中斷,當前線程指運行this.interrupted()的線程。同是具備清除狀態的功能,及若是連續兩次調用該方法,則第二次調用將返回false。
  2. this.isInterrupted():測試線程是否已經中斷。不具有清除狀態功能。

2.7 yield()方法

yield()方法的做用是放棄當前的CPU資源,讓給其餘的任務去佔用CPU執行的時間。可是放棄的時間不肯定,有可能剛放棄,立刻又獲取了CPU的時間片。

2.8 中止線程

中止線程意味着在線程處理完任務以前停掉正在作的操做,也就是放棄當前操做。
有三種中止線程的方式:
中止一個線程可使用Thread.stop()方法,但最好不要使用,雖然它確實能夠中止一個正在運行的線程,可是這個方法是不安全的,並且已經被廢棄。 大多數中止線程的操做使用Thread.interrupt()方法,但這個方法不會終止一個正在運行的線程,還須要加入一個判斷才能夠完成線程的中止。

  1. 使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
  2. 使用stop方法強行終止線程(這個方法不推薦使用,由於stop和suspend、resume同樣,也可能發生不可預料的結果)。
  3. 使用interrupt方法中斷線程。

使用退出標誌退出線程:
當run方法執行完後,線程就會退出。但有時run方法是永遠不會結束的。如在服務端程序中使用線程進行監聽客戶端請求,或是其餘的須要循環處理的任務。在這種狀況下,通常是將這些任務放在一個循環中,如while循環。若是想讓循環永遠運行下去,可使用while(true){……}來處理。但要想使while循環在某一特定條件下退出,最直接的方法就是設一個boolean類型的標誌,並經過設置這個標誌爲true或false來控制while循環是否退出。

package chapter2;  
   
public class ThreadFlag extends Thread  {  
    public volatile boolean exit = false;  
   
    public void run(){  
        while (!exit){
             //do something
        };  
    }  
    public static void main(String[] args) throws Exception{  
        ThreadFlag thread = new ThreadFlag();  
        thread.start();  
        sleep(5000); // 主線程延遲5秒  
        thread.exit = true;  // 終止線程thread  
        thread.join();  
        System.out.println("線程退出!");  
    }  
}  
複製代碼

使用stop方法終止線程:
使用stop方法能夠強行終止正在運行或掛起的線程。咱們可使用以下的代碼來終止線程:

thread.stop();  
複製代碼

雖然使用上面的代碼能夠終止線程,但使用stop方法是很危險的,就象忽然關閉計算機電源,而不是按正常程序關機同樣,可能會產生不可預料的結果,所以,並不推薦使用stop方法來終止線程。
使用interrupt方法終止線程:
使用interrupt方法來終端線程可分爲兩種狀況:

  1. 線程處於阻塞狀態,如使用了sleep,同步鎖的wait,socket中的receiver,accept等方法時,會使線程處於阻塞狀態。當調用線程的interrupt()方法時,會拋出InterruptException異常。阻塞中的那個方法拋出這個異常,經過代碼捕獲該異常,而後break跳出循環狀態,從而讓咱們有機會結束這個線程的執行。一般不少人認爲只要調用interrupt方法線程就會結束,其實是錯的, 必定要先捕獲InterruptedException異常以後經過break來跳出循環,才能正常結束run方法。
public class ThreadSafe extends Thread {
    public void run() { 
        while (true){
            try{
                    Thread.sleep(5*1000);//阻塞5妙
                }catch(InterruptedException e){
                    e.printStackTrace();
                    break;//捕獲到異常以後,執行break跳出循環。
                }
        }
    } 

複製代碼
  1. 使用while(!isInterrupted()){……}來判斷線程是否被中斷。
public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){
            //do something, but no throw InterruptedException
        }
    } 
}
複製代碼

爲何要區分進入阻塞狀態和和非阻塞狀態兩種狀況了,是由於當阻塞狀態時,若是有interrupt()發生,系統除了會拋出InterruptedException異常外,還會調用interrupted()函數,調用時能獲取到中斷狀態是true的狀態,調用完以後會復位中斷狀態爲false,因此異常拋出以後經過isInterrupted()是獲取不到中斷狀態是true的狀態,從而不能退出循環,所以在線程未進入阻塞的代碼段時是能夠經過isInterrupted()來判斷中斷是否發生來控制循環,在進入阻塞狀態後要經過捕獲異常來退出循環。所以使用interrupt()來退出線程的最好的方式應該是兩種狀況都要考慮。

2.9 暫停線程

暫停線程意味着此線程還能夠恢復運行,可使用suspend()方法暫停線程,使用resume()方法恢復線程。

public class MyRun extends Thread {
    private long i = 0;
    public long getI(){
        return i;
    }
    public void setI(long i){
        this.i = i;
    }
    @Override
    public void run() {
        while (true){
            i++;
        }
    }
}
複製代碼
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        myRun.start();
        Thread.sleep(3000);
        myRun.suspend();
        System.out.println("A= " + System.currentTimeMillis() + " i = " + myRun.getI());
//        myRun.resume();
        Thread.sleep(5000);
//        myRun.suspend();
        System.out.println("B= " + System.currentTimeMillis() + " i = " + myRun.getI());
    }
}
複製代碼

suspend和resume方法能夠是線程暫停和重啓,可是若是使用不當,極易形成公共的同步對象的獨佔和由於線程暫停而致使的數據不一樣步的狀況。

2.10 守護線程

在java線程中有兩種線程,一種是用戶線程,一種是守護線程。
守護線程是一種特殊得線程,它得特性有「陪伴」得含義,當進程中不存在非守護線程,則守護線程自動銷燬。典型得守護線程就是垃圾回收線程。
可使用setDaemon()設置線程爲守護線程。不能把一個正在運行得線程設置爲守護線程,因此,setDaemon()方法必須在start()方法前面。而且守護線程中產生得線程也是守護線程。

相關文章
相關標籤/搜索