1. 程式人生 > >多執行緒學習-01

多執行緒學習-01

1.程式、程序、執行緒概念

程式(Program):為了完成某個特定的任務、功能,而用某種程式語言編寫的一組指令的集合。(靜止的程式碼)

程序(Process):作業系統排程程式,是程式的一次執行,是正在執行的一個程式。(執行中程式碼)

   如果一個程式執行兩次,就會有兩個程序。
   在作業系統中,程序是資源分配、排程和管理的最小單位。每個程序在記憶體中是獨立的。

執行緒(Thread):執行緒是程序中的一條執行路徑。

  一個程序中可能有多條路徑同時執行,多個並行的(concurrent)執行緒。
   執行緒是CPU處理器排程的最小單元。
   執行緒可以對程序的記憶體空間和資源進行訪問,並與同一個程序中的其他執行緒共享,它們從同一個堆中分配物件。由於執行緒間的通訊是在
同一個地址空間上進行的,所以不需要額外的通訊機制,這就使得通訊更簡便而且資訊傳遞的速度也更快。但是也存在安全問題。
因為其中一個執行緒對共享的系統資源的操作都會給其他執行緒帶來影響。
由此可知,多執行緒中的同步(和諧)是非常重要的問題。
舉例說明:

(1)單執行緒:

一旦前面的車,阻塞了,後面的車只能一直等待
(2)多執行緒:
其中一條路阻塞了,其他路還能繼續走
注  意:如果單核CPU,其實是一種假的多執行緒,因為在一個時間單元內,也只能執行一個執行緒的任務。例如:雖然有多車道,
但是收費站只有一個工作人員在收費,只有收了費才能通過,那麼CPU就好比那個收費人員。如果有某個人不想交錢,
那麼那個收費人員可以把他“掛起”(晾著他,等他想通了,準備好了錢,再去收費)。

單執行緒:  多執行緒:

              

但是因為CPU時間單元特別短,因此感覺不出來。
如果是多核的話,才能更好的發揮多執行緒的效率。(現在的伺服器都是多核的)
說明:一個Java應用程式java.exe,其實至少有三個執行緒:main()主執行緒,gc()垃圾回收執行緒,
異常處理執行緒。當然如果發生異常,會影響主執行緒。

多執行緒實現方式

Runnable介面實現多執行緒
Callable介面實現多執行緒

Runnable介面實現多執行緒

即在主執行緒以外開啟另一個執行緒呢?

Thread類

在Java中負責執行緒的這個功能的是java.lang.Thread這個類
可以通過建立Thread的例項來建立新的執行緒
每個執行緒都是通過某個特定Thread物件所對應的方法run()來完成其操作的,run()又被稱為執行緒體。
必須通過呼叫Thread類的start()方法來啟動一個執行緒。


JDK1.5之前建立新執行執行緒有兩種方法:
  1.一種方法是將類宣告為 Thread 的子類。重寫run()
  2.宣告實現 Runnable 介面的類
    該類然後實現 run 方法。然後可以分配該類的例項,在建立 Thread 時作為一個引數來傳遞並啟動。

示例程式碼

package com.thread;

public class Coding extends Thread{

	@Override
	public void run() {
		for(int i=1;i<=100;i++){
			System.out.println("編寫程式碼....,寫了"+i+"行程式碼");
		}
	}
}
package com.thread;

public class Chat implements Runnable{

	@Override
	public void run() {
		for(int i=1;i<=100;i++){
			System.out.println("聊天....,發了"+i+"條訊息");
		}
	}
}
package com.thread;

public class TestThread {

	public static void main(String[] args) {
		Coding c = new Coding();
		Chat chat = new Chat();
		
		//不能直接呼叫run(),必須通過start啟動執行緒。由CPU決定JVM什麼時候執行誰的run()
		c.start();
		new Thread(chat).start();     //靜態代理模式   target = chat
		
		for(int i=1;i<=100;i++){
			System.out.println("main聽歌...i="+i);
		}
	}
}

注意:啟動執行緒

public synchronized void start() {
        ......
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
          .......
        }
}
private native void start0();

結論:

(1)如果自己手動呼叫run()方法,那麼就只是普通方法,沒有啟動多執行緒模式。

(2)run()方法由JVM呼叫,什麼時候呼叫,執行的過程控制都有作業系統的CPU排程決定

(3)想要啟動多執行緒,必須呼叫start方法

(4)一個執行緒物件只能呼叫一次start()方法啟動,如果重複呼叫了,則將丟擲以上的異常“IllegalThreadStateException”

繼承方式和實現方式的區別:

【實現方法的好處】

1)避免了單繼承的侷限性

2)多個執行緒可以共享同一個介面子類的物件target,非常適合多個相同執行緒來處理同一份資源。

案例:

龜兔賽跑
1. 案例題目描述:編寫龜兔賽跑多執行緒程式,設賽跑長度為30米
1)烏龜和兔子每跑完10米輸出一次結果。
2)兔子的速度是10米每秒,兔子每跑完10米休眠的時間10秒
3)烏龜的速度是1米每秒,烏龜每跑完10米的休眠時間是1秒
2. 案例完成思路要求:
1)烏龜一個執行緒,兔子一個執行緒
2)兩個執行緒同時開啟
3)提示:可以使用Thread.sleep(毫秒數)來模擬耗時
package com.thread;

public class Race implements Runnable {
	private String name;
	private long speed;
	private long restTime;
	private long distance;

	public Race(String name, long distance, long speed, long restTime) {
		super();
		this.name = name;
		this.distance = distance;
		this.speed = speed;
		this.restTime = restTime;
	}

	@Override
	public void run() {
		int sum = 0;
		long start = System.currentTimeMillis();
		while (sum < distance) {
			try {
				Thread.sleep(speed);// 每米距離運動員的時間
			} catch (InterruptedException e) {
				e.printStackTrace();
				return ;
			}
			sum++;
			try {
				if (sum % 10 == 0) {
					System.out.println(name+"跑了"+sum+"米");
					if(sum < distance){// 每10米休息一下
						System.out.println(name+"正在休息....");
						Thread.sleep(restTime);
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
				return ;
			}
		}
		long end = System.currentTimeMillis();
		System.out.println(name+"跑"+sum+"米用時"+(end-start)+"毫秒");
	}
}
package com.thread;

public class TestRace {

	public static void main(String[] args) {
		Race rabbit = new Race("兔子", 20, 10, 10000);
		Race tortoise = new Race("烏龜", 20, 100, 1000);
		new Thread(rabbit).start();
		new Thread(tortoise).start();
	}
}

Callable介面實現多執行緒

JDK1.5之後增加的,在java.util.concurrent包,Callable和Future介面
優點:可以獲取返回值
缺點:繁瑣

Callable介面

Callable是類似於Runnable的介面,實現Callable介面的類和實現Runnable的類都是可被其他執行緒執行的任務。

Callable和Runnable有幾點不同:

Callable定義的方法是call(),而Runnable定義的方法是run()。
call()方法可以有返回值,執行Callable任務可拿到一個Future物件,而run()不能有返回值。
call()方法可以丟擲異常,而run()不能丟擲異常。

Future介面

Future表示非同步計算的結果。

 它提供了檢查計算是否完成的方法,以等待計算的完成,並檢查計算的結果。
 通過Future物件可瞭解任務執行情況,可取消任務的執行,還可以獲取任務執行的結果。
 Future的cancel(boolean mayInterruptIfRunning)方法可以取消任務的執行,true表示立即中斷,false表 
 示繼續等待它的完成
 get()方法等待計算完成,獲取計算結果。

啟動方式一:FutureTask + Thread

FutureTask實現了兩個介面:Runnable和Future介面。
  因為實現了Runnable介面,所以FutureTask的物件可以作為Thread的target啟動。
  因為實現了Future介面,所以可以獲取返回值。
FutureTask的構造方法:
  public FutureTask(Callable<V> callable)
  public FutureTask(Runnable runnable,V result)
Thread的相關的構造方法:
  public Thread(Runnable target)
package com.call;

import java.util.concurrent.FutureTask;

public class TestCallable {

	public static void main(String[] args) throws Exception {
		Race rabbit = new Race("兔子", 20, 10, 1000000);
		Race tortoise = new Race("烏龜", 20, 100, 1000);

		FutureTask<Long> f1 = new FutureTask<Long>(rabbit);
		FutureTask<Long> f2 = new FutureTask<Long>(tortoise);

		new Thread(f1).start();
		new Thread(f2).start();

		// 如果沒有這三句,那麼兩個get()都將等待執行緒執行完才返回結果
		Thread.sleep(10000);// 比如規定比賽時間不能超過10秒
		f1.cancel(true);// 為true,比賽時間到,不管兔子是否跑完,都結束兔子的比賽
		f2.cancel(true);// 為true,比賽時間到,不管烏龜是否跑完,都結束兔子的比賽

		System.out.println("比賽結束");
		Long num1 = -1L;
		Long num2 = -1L;
		try {
			num1 = f1.get();
		} catch (Exception e) {
			System.out.println("兔子沒跑完,比賽成績無效");
		}
		try {
			num2 = f2.get();
		} catch (Exception e) {
			System.out.println("烏龜沒跑完,比賽成績無效");
		}
		if(num1>num2){
			System.out.println("兔子贏");
		}else if(num1<num2){
			System.out.println("烏龜贏");
		}else{
			System.out.println("難分伯仲");
		}
	}
}

package com.call;

import java.util.concurrent.Callable;

public class Race implements Callable<Long> {
	private String name;
	private long speed;
	private long restTime;
	private long distance;

	public Race(String name, long distance, long speed, long restTime) {
		super();
		this.name = name;
		this.distance = distance;
		this.speed = speed;
		this.restTime = restTime;
	}

	@Override
	public Long call() throws Exception {
		int sum = 0;
		long start = System.currentTimeMillis();
		while (sum < distance) {
			Thread.sleep(speed);// 每米距離運動員的時間
			sum++;
			if (sum % 10 == 0) {
				System.out.println(name+"跑了"+sum+"米");
				if(sum < distance){// 每10米休息一下
					System.out.println(name+"正在休息....");
					Thread.sleep(restTime);
				}
			}
		}
		long end = System.currentTimeMillis();
		System.out.println(name+"跑"+sum+"米用時"+(end-start)+"毫秒");
		return end - start;
	}
}

啟動方式二:Executors工廠 + ExecutorService排程服務

package com.call;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestCallable2 {

	public static void main(String[] args) throws Exception {
		Race rabbit = new Race("兔子", 20, 10, 1000000);
		Race tortoise = new Race("烏龜", 20, 100, 1000);
	
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		Future<Long> f1 = threadPool.submit(rabbit); 
		Future<Long> f2 = threadPool.submit(tortoise);

		// 如果沒有這三句,那麼兩個get()都將等待執行緒執行完才返回結果
		Thread.sleep(10000);// 比如規定比賽時間不能超過10秒
		f1.cancel(true);// 為true,比賽時間到,不管兔子是否跑完,都結束兔子的比賽
		f2.cancel(true);// 為true,比賽時間到,不管烏龜是否跑完,都結束兔子的比賽

		System.out.println("比賽結束");
		Long num1 = -1L;
		Long num2 = -1L;
		try {
			num1 = f1.get();
		} catch (Exception e) {
			System.out.println("兔子沒跑完,比賽成績無效");
		}
		try {
			num2 = f2.get();
		} catch (Exception e) {
			System.out.println("烏龜沒跑完,比賽成績無效");
		}
		if(num1>num2){
			System.out.println("兔子贏");
		}else if(num1<num2){
			System.out.println("烏龜贏");
		}else{
			System.out.println("難分伯仲");
		}

		 threadPool.shutdown();//停止服務
	}

}
package com.call;

import java.util.concurrent.Callable;

public class Race implements Callable<Long> {
	private String name;
	private long speed;
	private long restTime;
	private long distance;

	public Race(String name, long distance, long speed, long restTime) {
		super();
		this.name = name;
		this.distance = distance;
		this.speed = speed;
		this.restTime = restTime;
	}

	@Override
	public Long call() throws Exception {
		int sum = 0;
		long start = System.currentTimeMillis();
		while (sum < distance) {
			Thread.sleep(speed);// 每米距離運動員的時間
			sum++;
			if (sum % 10 == 0) {
				System.out.println(name+"跑了"+sum+"米");
				if(sum < distance){// 每10米休息一下
					System.out.println(name+"正在休息....");
					Thread.sleep(restTime);
				}
			}
		}
		long end = System.currentTimeMillis();
		System.out.println(name+"跑"+sum+"米用時"+(end-start)+"毫秒");
		return end - start;
	}
}

執行緒的生命週期

一個完整的生命週期中通常要經歷如下的五種狀態:
 新建: 當一個Thread類或其子類的物件被宣告並建立時,新生的執行緒物件處於新建狀態
 就緒:處於新建狀態的執行緒被start()後,將進入執行緒佇列等待CPU時間片,此時它已具備了執行的條件
 執行:當就緒的執行緒被排程並獲得處理器資源時,便進入執行狀態, run()方法定義了執行緒的操作和功能
 阻塞:在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態
 死亡:執行緒完成了它的全部工作或執行緒被提前強制性地中止   

停止執行緒

1、自然終止:執行緒體正常執行完畢
2、外部干涉:推薦使用標識
 執行緒類中定義執行緒體使用的標識
 執行緒體使用該標識
 對外提供改變標識的方法
 外部根據條件呼叫該方法即可
3、Thread.stop()已過時
package com.stop;

public class Race implements Runnable {
	private String name;
	private long speed;
	private long restTime;
	private long distance;
	private boolean flag = true;//執行緒類中定義執行緒體使用的標識

	public Race(String name, long distance, long speed, long restTime) {
		super();
		this.name = name;
		this.distance = distance;
		this.speed = speed;
		this.restTime = restTime;
	}

	@Override
	public void run() {
		int sum = 0;
		long start = System.currentTimeMillis();
		while (flag) {//執行緒體使用該標識
			try {
				Thread.sleep(speed);// 每米距離運動員的時間
			} catch (InterruptedException e) {
				e.printStackTrace();
				flag = false;
			}
			sum++;
			try {
				if (sum % 10 == 0) {
					System.out.println(name+"跑了"+sum+"米");
					if(sum < distance){// 每10米休息一下
						System.out.println(name+"正在休息....");
						Thread.sleep(restTime);
					}else{
						flag = false;
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
				flag = false;
			}
		}
		long end = System.currentTimeMillis();
		System.out.println(name+"跑"+sum+"米用時"+(end-start)+"毫秒");
	}
    //對外提供改變標識的方法
	public void stop() {
		this.flag = false;
	}
	
}
package com.stop;

import java.util.Random;

public class TestRace {

	public static void main(String[] args)throws Exception {
		Race rabbit = new Race("兔子", 20, 10, 10000);
		Race tortoise = new Race("烏龜", 20, 100, 1000);
		new Thread(rabbit).start();
		new Thread(tortoise).start();
		
		for(int i=0;i<100;i++){
			Random r = new Random();
			int n = r.nextInt(100);
			System.out.println("n="+n);
            //外部根據條件呼叫該方法即可,外部干涉
			if(n%10==0){
				rabbit.stop();
				tortoise.stop();
                //注意,rabbit和tortoise不一定立馬停止。就好比公安局打電話給收費站說攔截某車,某車得到收費員那裡才能停下,沒到那裡不會停。
			}
		}
	}
}

Thread類的方法

構造方法
Thread():建立新的Thread物件
Thread(String threadname):建立執行緒並指定執行緒例項名
Thread(Runnable target):指定建立執行緒的目標物件,它實現了Runnable介面中的run方法
Thread(Runnable target, String name):建立新的Thread物件 
必用方法
1、public void run()執行緒在被排程時執行的操作
2、public void start()啟動執行緒,並執行物件的run()方法
其他方法
1、public final boolean isAlive()判斷執行緒是否還活著
2、public final String getName()返回執行緒的名稱
3、public final void setName(String name)設定該執行緒名稱
4、public static Thread currentThread()返回當前執行緒。在Thread子類中就是this,通常用於主執行緒和Runnable實現類
執行緒的優先順序
執行緒的優先順序控制
  MAX_PRIORITY(10);    
  MIN _PRIORITY (1);  
  NORM_PRIORITY (5);
涉及的方法:
  getPriority() :返回執行緒優先值 
  setPriority(int newPriority) :改變執行緒的優先順序
執行緒建立時繼承父執行緒的優先順序
說明:優先順序只是代表概率,不代表絕對的先後順序
控制執行緒的方法
1、public final void join():等待該執行緒終止。 (有併線的效果)
2、public static void yield():暫停當前正在執行的執行緒物件,並執行其他執行緒。
3、public static void sleep(long millis):在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作
受到系統計時器和排程程式精度和準確性的影響。該執行緒不丟失任何監視器的所屬權,即不釋放鎖。
package com.thread.method;

import java.text.SimpleDateFormat;
import java.util.Date;

/*
 * 廣告倒計時
 */
public class TestSleep2 {

	public static void main(String[] args)throws Exception {
		SimpleDateFormat sf = new SimpleDateFormat("mm:ss");
		Date start =  sf.parse("00:05");
		Date end =  sf.parse("00:00");
		long time =start.getTime();
		do{
			Date date = new Date(time);
			System.out.println(sf.format(date));
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			time -= 1000;
			if(end.getTime()>=time){
				break;
			}
		}while(true);
	}
}
package com.thread.method;

/*
 * 搶票
 */
public class TestSleep3 {

	public static void main(String[] args)throws Exception {
		Ticket t = new Ticket();
		new Thread(t,"視窗一").start();
		new Thread(t,"視窗二").start();
		new Thread(t,"視窗三").start();
	}
}
class Ticket implements Runnable{
	private int num = 10;

	public void run(){
		while(true){
			if(num<=0){
				break;
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"搶到了一張票,剩餘:"+(--num));
		}
	}
}

出現執行緒安全問題後續補充