多線程1
10.1.什麼是多線程?
- 什麼是程序?
程序:在作業系統中運作的某個軟體/某個程式(主要是指在記憶體中)。[動态]
任何軟體/程式要運作都要被加載到記憶體中,而記憶體負責運作這個軟體/程式所需要的那些記憶體空間,就被稱為目前軟體在記憶體中的一個程序。
-
程序需要依賴于作業系統
程序就是在作業系統中動态運作的靜态代碼。
-
什麼是線程?
線程就是在作業系統中動态運作的靜态代碼【程序】中的某一項具體功能的執行過程【執行軌迹/執行線索】。
例如:
我們在window作業系統上打開“暴風影音”播放電影,此時“暴風影音”就會在window作業系統中産生一個程序;打開“暴風影音”播放電影的時候有畫面,聲音,中文字幕等等,這些畫面,聲音,中文字幕就是這個“暴風影音”程序中的多個線程。
程序 | 線程 |
---|---|
依賴作業系統 | 依賴程序 |
程序與程序之間的互動很困難 | 線程與線程之間的互動很容易 |
3. 什麼是多線程?
多線程:某一個程式在運作的時候可能會産生多個不同的執行線索【執行軌迹】,這些多個不同的執行線索【執行軌迹】共同運作的情況就是多線程。
往往我們會感覺到這些多個不同的執行線索【執行軌迹】同時執行,實際上這時一種錯覺假象,實際上當這些多個不同的執行線索【執行軌迹】在運作的時候,某一個時刻隻有一個執行線索【執行軌迹】在運作,隻是這多個不同的執行線索【執行軌迹】快速的切換而已。
4.為什麼使用多線程?
1.使用多線程的目的就是為了提高程式的執行效率。
2.解決并發問題。
并行和并發有什麼差別?
并行:多個處理器或多核處理器同時處理多個任務。
并發:多個任務在同一個 CPU 核上,按細分的時間片輪流(交替)執行,從邏輯上來看那些任務是同時執行。
如下圖:【并發 = 兩個隊列和一台咖啡機】 【并行 = 兩個隊列和兩台咖啡機】
10.2.多線程的建立方式以及差別
Java中的線程
當一個java程式啟動運作以後,至少有2個線程在運作。
- 主線程,就是java程式的主方法執行線索
- 垃圾回收線程。
Java中多線程的建立方式【4種】
第一種,通過繼承Thread類建立線程類
- 建立一個類,繼承Thread類
- 重寫run方法
- 将需要由線程執行的具體動作寫入run方法
package com.wangxing.test1;
/**
* 1.建立一個類,繼承Thread類
* 2.重寫run方法
* 3.将需要由線程執行的具體動作寫入run方法
* @author Administrator
*
*/
public class TestThread extends Thread{
//通過繼承Thread類建立線程類
@Override
public void run() {
//得到目前線程的名稱
String name=Thread.currentThread().getName();
for(int i=0;i<=100;i++){
System.out.println(name+"----i=="+i);
}
}
}
測試類:
package com.wangxing.test1;
public class testmain {
public static void main(String[] args) {
//啟動線程
//1.建立線程對象
//2.通過線程對象調用start方法啟動線程
TestThread tth1=new TestThread();
tth1.start();
TestThread tth2=new TestThread();
tth2.start();
}
}
第二種,通過實作Runnable接口建立線程類
- 建立一個類,實作Runnable接口
- 重寫run方法
- 将需要由線程執行的具體動作寫入run方法
package com.wangxing.test2;
/**
* 通過實作Runnable接口建立線程類
* 1. 建立一個類,實作Runnable接口
* 2. 重寫run方法
* 3. 将需要由線程執行的具體動作寫入run方法
* @author Administrator
*
*/
public class MyThread implements Runnable {
@Override
public void run() {
//得到目前線程的名稱
String name=Thread.currentThread().getName();
for(int i=0;i<=100;i++){
System.out.println(name+"----i=="+i);
}
}
}
測試類:
package com.wangxing.test2;
public class TestMain {
public static void main(String[] args) {
/*
* 1.建立有線程類執行的目标對象【實作Runnable接口的java類對象】
* 2.建立線程對象【java.lang.Thread類的對象】
* public Thread(Runnable target)
* public Thread(Runnable target, String name)
* 3.調用start方法啟動線程運作
*/
MyThread mt=new MyThread();
Thread th1=new Thread(mt);
th1.start();
Thread th2=new Thread(mt);
th2.start();
}
}
第三種,通過Callable和Future接口建立線程
通過這兩個接口建立線程,你要知道這兩個接口的作用,下面我們就來了解這兩個接口:
通過實作Runnable接口建立多線程時,Thread類的作用就是把run()方法包裝成線程的執行體,那麼,是否可以直接把任意方法都包裝成線程的執行體呢?
從JAVA5開始,JAVA提供提供了Callable接口,該接口是Runnable接口的增強版,Callable接口提供了一個call()方法可以作為線程執行體,但call()方法比run()方法功能更強大,call()方法的功能的強大展現在:
1、call()方法可以有傳回值;
2、call()方法可以聲明抛出異常;
從這裡可以看出,完全可以提供一個Callable對象作為Thread的target,而該線程的線程執行體就是call()方法。
問題是:Callable接口是JAVA新增的接口,而且它不是Runnable接口的子接口,是以Callable對象不能直接作為Thread的target。還有一個原因就是:call()方法有傳回值,call()方法不是直接調用,而是作為線程執行體被調用的,是以這裡涉及擷取call()方法傳回值的問題。
于是,JAVA5提供了Future接口來代表Callable接口裡call()方法的傳回值,并為Future接口提供了一個FutureTask實作類,該類實作了Future接口,并實作了Runnable接口,是以FutureTask可以作為Thread類的target,同時也解決了Callable對象不能作為Thread類的target這一問題。
在Future接口裡定義了如下幾個公共方法來控制與它關聯的Callable任務:
1、boolean cancel(boolean mayInterruptIfRunning):試圖取消Future裡關聯的Callable任務;
2、V get():傳回Callable任務裡call()方法的傳回值,調用該方法将導緻程式阻塞,必須等到子線程結束以後才會得到傳回值;
3、V get(long timeout, TimeUnit unit):傳回Callable任務裡call()方法的傳回值。該方法讓程式最多阻塞timeout和unit指定的時間,如果經過指定時間後,Callable任務依然沒有傳回值,将會抛出TimeoutException異常;
4、boolean isCancelled():如果Callable任務正常完成前被取消,則傳回true;
5、boolean isDone():如果Callable任務已經完成, 則傳回true;
這種方式建立并啟動多線程的步驟如下:
1、建立Callable接口實作類,并實作call()方法,該方法将作為線程執行體,且該方法有傳回值,再建立Callable實作類的執行個體;
2、使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的傳回值;
3、使用FutureTask對象作為Thread對象的target建立并啟動新線程;
4、調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值。
package com.wangxing.test3;
import java.util.concurrent.Callable;
/**
* 第三種方法:
* 建立Callable接口實作類,并實作call()方法,該方法将作為線程執行體,
* 且該方法有傳回值,再建立Callable實作類的執行個體;
* 2、使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的傳回值;
* 3、使用FutureTask對象作為Thread對象的target建立并啟動新線程;
* 4、調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值。
* @author Administrator
*
*/
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
//得到目前線程的名稱
String name=Thread.currentThread().getName();
for(int i=1;i<=100;i++) {
System.out.println(name+"--i=="+i);
}
return name+"執行完畢";
}
}
測試類:
package com.wangxing.test3;
import java.util.concurrent.FutureTask;
public class TestMain {
/*
* 1、boolean cancel(boolean mayInterruptIfRunning):試圖取消Future裡關聯的Callable任務;
* 2、V get():傳回Callable任務裡call()方法的傳回值,調用該方法将導緻程式阻塞,必須等到子線程結束以後才會得到傳回值;
* 3、V get(long timeout, TimeUnit unit):傳回Callable任務裡call()方法的傳回值。該方法讓程式最多阻塞timeout和unit指定的時間,如果經過指定時間後,Callable任務依然沒有傳回值,将會抛出TimeoutException異常;
* 4、boolean isCancelled():如果Callable任務正常完成前被取消,則傳回true;
* 5、boolean isDone():如果Callable任務已經完成, 則傳回true;
*/
public static void main(String[] args)throws Exception {
//目标對象
MyCallable mc=new MyCallable();
//将目标對象包裝成Runnable的子類
/*
FutureTask是RunnableFuture子類,
RunnableFuture是Runnable的子類
*/
FutureTask<Integer> task1=new FutureTask(mc);
FutureTask<Integer> task2=new FutureTask(mc);
//線程對象
Thread Th1=new Thread(task1);
Thread Th2=new Thread(task2);
//調用start方法啟動線程
Th1.start();
Th2.start();
//測試方法
//5、boolean isDone():如果Callable任務已經完成, 則傳回true;
//2、V get():傳回Callable任務裡call()方法的傳回值,調用該方法将導緻程式阻塞,必須等到子線程結束以後才會得到傳回值;
Object returntask1=task1.get();
System.out.println(returntask1);
Object returntask2=task2.get();
System.out.println(returntask2);
}
}
第四種,通過線程池建立多線程【使用的比較少,是以不強調】
差別:
繼承Thread類 | 實作Runnable接口 | Callable和Future接口 |
建立新類繼承Thread類重寫run方法 | 建立新類實作Runnable接口重寫run方法 | 建立新類實作Callable接口重寫call方法,注意Callable接口的泛型類型 |
run方法沒有傳回值,不能聲明抛出異常 | call方法有傳回值,通過Future接口提供的get方法得到傳回值,可以聲明抛出異常 | |
建立Thread類的子類對象【線程對象】,通過子類對象調用start方法啟動線程 | 建立實作實作Runnable接口的子類對象【目标對象】,通過Thread的構造方法,關聯目标對象,建立線程對象【Thread類的對象】,通過線程對象調用start方法啟動線程 | 建立實作Callable接口的子類對象【目标對象】,通過Future接口的子類FutureTask将目标對象包裝成Runnable接口的子類對象,通過Thread的構造方法,關聯FutureTask包裝成的Runnable接口的子類對象,建立線程對象【Thread類的對象】,通過線程對象調用start方法啟動線程 |
無法資源共享 | 可以資源共享 | 可以資源共享 |
不考慮資源共享時 | 考慮資源共享時 | 考慮資源共享時,異步程式設計 |
資源是否共享:
繼承Thread類:
package com.wangxing.test4;
public class TestThread extends Thread{
private int piao=5;
public void run(){
boolean flag=true;
while(flag){
if(piao>0){
piao =piao-1;
System.out.println(Thread.currentThread().getName()+"賣出一張票,還剩"+piao+"張");
}
else{
flag=false;
}
}
}
}
測試類:
package com.wangxing.test4;
import java.util.concurrent.FutureTask;
public class TestMain {
public static void main(String[] args) {
//第一種多線程---資源不共享
TestThread Th1=new TestThread();
TestThread Th2=new TestThread();
Th1.start();
Th2.start();
}
}
實作Runnable接口:
package com.wangxing.test4;
public class MyThread implements Runnable{
private int piao=5;
@Override
public void run() {
boolean flag=true;
while(flag){
if(piao>0){
piao =piao-1;
System.out.println(Thread.currentThread().getName()+"賣出一張票,還剩"+piao+"張");
}
else{
flag=false;
}
}
}
}
測試類:
package com.wangxing.test4;
import java.util.concurrent.FutureTask;
public class TestMain {
public static void main(String[] args) {
//第二種多線程---資源共享
MyThread mt=new MyThread();
Thread th1=new Thread(mt);
th1.start();
Thread th2=new Thread(mt);
th2.start();
}
}
實作Callable和Future接口:
package com.wangxing.test4;
import java.util.concurrent.Callable;
public class MyCallable implements Callable{
private int piao=5;
@Override
public Object call() throws Exception {
boolean flag=true;
while(flag){
if(piao>0){
piao =piao-1;
System.out.println(Thread.currentThread().getName()+"賣出一張票,還剩"+piao+"張");
}
else{
flag=false;
}
}
return null;
}
}
測試類:
package com.wangxing.test4;
import java.util.concurrent.FutureTask;
public class TestMain {
public static void main(String[] args) {
//第三種多線程---資源共享
MyCallable mc=new MyCallable();
FutureTask fu=new FutureTask<>(mc);
Thread th1=new Thread(fu);
Thread th2=new Thread(fu);
th2.start();
th1.start();
}
}