Java基礎之多線程沒那么復雜!
線程是程序執行的一條路徑,一個進程中可以包含多條線程;多線程并發執行可以提高程序的效率
</br>
2.進程和線程之間的關系操作系統可以同時執行多個任務,每個任務就是進程;進程可以同時執行多個任務,每個任務就是線程。
<br>
3.多線程的應用場景紅蜘蛛同時共享屏幕給多個電腦
迅雷開啟多條線程一起下載
QQ開啟多人聊天
服務器同時處理多個客戶的請求
<br>
多線程并行和并發的區別
- 并行性和并發性是兩個概念,并行性指在同一時刻,有多條指令在多個處理器上同時執行<br>
- 并發性指的是同一時刻只有一條指令被執行,但多個進程指令被快速切換執行是的在宏觀上具有多個進程被同時執行的效果
<br>
Java程序運行原理和JVM的啟動是多線程的嗎?- java程序運行原理
- Java命令啟動jvm,啟動jvm等于啟動一個應用程序,也就是啟動了一個進程,該進程會自動啟動一個“主線程”,然后主線程去調用某個類的main方法
- Jvm啟動是多線程的的嗎
- JVM啟動至少啟動了垃圾回收線程和主線程,所以是多線程
<br>
多線程的實現方式 1. 繼承Thread類創建線程類(1) 定義Thread的子類,并重寫該類的run方法,該ru的執行體就代表了線程需要完成的任務,因此run()方法被稱為線程執行體
(2) 創建Thread子類的實例,即創建了線程對象<br>
(3) 調用線程對象的start()方法啟動該線程
package Demo;
public class Demo1__Thread {
public static void main(String[] args) {
ChThread t=new ChThread();
t.start();
for(int i=0;i<1000;i++) {
System.out.println("我是主方法!");
}
}
}
class ChThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<1000;i++) {
System.out.println("我是run方法");
}
}
}
上述代碼的執行驗證了多線程,如果上述程序的執行過程是多線程的話,會發現屏幕中的 ”我是主方法”和“我是run方法” 的字樣是交替出現的,這說明了程序的的執行過程為并行執行Thread類的Start()方法啟動run()方法的線程,和主方法中的執行同時進行。
<br>
2.實現Runnable接口創建線程類(1) 定義一個實現了Runnable接口的實現類<br>
(2) 創建Runnable實現類的實例<br>
(3) 將創建的實例作為Thread類的target類創建Thread對象,該對象才是真正的線程對象 <br>
(4) 用創建的Thread對象啟動線程
package xianchenhg;
public class Thread_Running implements Runnable {
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new Thread(new Thread_Running(),"線程1").start();
new Thread(new Thread_Running(),"線程2").start();
}
}
}
}
3.實現Callable接口和Future接口創建多線程查看API文檔,會發現Runnable接口只定義了run()方法這一個抽象類,所以實現Runnable接口的實現類只有run()方法,僅作為線程執行體,所以,Runnable對象僅僅作為Thread對象的target,而實際的線程對象依然是Thread實例,Thread實例負責執行target的run()方法。
(1)Callable接口更像是Runnable接口的增強版,相比較Runable接口,Call()方法新增捕獲和拋出異常的功能;Call()方法可以返回值<br>
(2)Future接口提供了一個實現類FutureTask實現類,FutureTaks類用來保存Call()方法的返回值,并作為Thread類的target。<br>
(3)調用FutureTask的get()方法來獲取返回值
package Demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* (1)創建Callable接口的實現類,并重寫call()方法,該call()方法作為線程的執行體,且有返回值
* (2)創建了Callable接口的實現類的實例,并用FutureTask()方法包裝對象,該FutureTask()對象實現了
* 將對象的返回值包裝的功能
* (3)使用FutureTask對象將Thread對象的target,創建并啟動線程
* (4)調用FutureTask對象的get()方法獲得子線程執行結束后的返回值
* */
public class Callable_Future implements Callable<Integer> {
@Override
public Integer call() throws Exception { //重寫Callable接口中的call()方法
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//創建Callable的對象
Callable_Future ca=new Callable_Future();
FutureTask<Integer> ft=new FutureTask<Integer>(ca);
for(int i=0;i<100;i++) {
//返回值主線程的名稱和執行代號
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new Thread(ft,"Callable線程").start();
//該方法將導致主線程被阻塞,直到call()方法結束并返回為止
//System.out.println("子線程的返回值"+ft.get());
}
}
try{
System.out.println("子線程的返回值"+ft.get());
}catch (Exception e) {
e.printStackTrace();
}
}
}
4.三種實現多線程的方式對比上面的程序中,FutufeTask方法的get()方法將獲得Call()方法的返回值,但是該方法將導致主線程受阻直到Call()方法結束并返回為止。
實現方式 | 重寫方法 | 啟動線程的方式 | 優點 | 缺點 |
---|---|---|---|---|
繼承Thread類 | run() | Thread對象.start() | 編程簡單,直接用this即可獲取當前對象 | 繼承Thread的子類不能繼承其他的類 |
實現Runnable接口 | run() | Thread對象.start() | 1.實現類可以繼承其他類<br> 2.多個對象共享一個target,形成了清晰的模型 | 編程復雜,必須使用Thread.currentThread()返回當前對象 |
實現Callable接口 | Call() | Thread對象.start() | 1.同Runnable接口<br>2.Call()方法有返回值并能拋出異常 | 同Runnable接口 |
5.線程的生命周期通過上面的對比發現,一般在項目中,我們使用Runnable接或者Callable接口來實現多線程。
6.控制線程
- 線程并不是創建過之后就開始執行的,也不是一直處于執行狀態,一個線程的生命周期一個又五個:新建、就緒、運行、阻塞、死亡。<br>2.線程的這五種狀態的原理參照《計算機組成原理2》多線程部分
1.join線程
1.Thread類中的成員方法,讓調用join()方法的線程處于等待狀態,直到子線程執行結束<br>2.join()方法通常由使用線程的程序調用,用于將一個線程拆分成若干小的線程執行,執行結束最后由主線程進行進一步的操作
代碼演示:
public class joinThread extends Thread {
//創建一個有參構造函數,用來為線程命名
public joinThread(String str) {
super(str);
}
//重寫run方法
public void run() {
for(int i=1;i<100;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)throws Exception {
//啟動子線程
new joinThread("子線程").start();
for(int i=0;i<100;i++) {
if(i==20) {
joinThread jt=new joinThread("新線程的子線程");
jt.start(); //啟動子線程
jt.join(); //讓主線程處于等待狀態
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
mian線程調用join之后,出去阻塞狀態,兩個子線程并發執行。直到兩個子程序執行結束之后,main線程才開始執行。
join()線程有如下三種重載形式
1.join() :<br>
2.join(long millis) :被join的線程的時間最長為millis毫秒,如果超過了這個millis則線程不被執行<br>
3.join(long millis,int nanos) :被join的線程的時間最長為millis毫秒+nanos微秒,如果超過了這個時間則線程不被執行 <br>
第三種重載行駛一般用不到,因為無論是計算機系統還是計算機硬件,還沒發精確到微秒
2.后臺線程
后臺線程運行于后臺,任務是為其他線程提供服務,也被稱為“守護線程”或“精靈線程”。JVM的垃圾回收機制就是一個典型的后臺線程。<br> 通過調用Thread類的setDaemon(true)方法將線程設置為后臺線程
代碼演示:
public class DaemonThread extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
DaemonThread dt=new DaemonThread();
dt.setDaemon(true);
dt.start();
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
運行結果:
1.Thread-0的后臺進程本該當i=999的時候才停止進行,但是程序中只進行到114次,這是因為所有的前臺進程結束之后,后天進程的存在也就是去了意義,所以后臺進程也跟著死亡。<br>
2.前臺線程死亡之后,JVM會通知后臺線程死亡,但是從后臺線程接收指令到做出反應需要一定的時間,這就是為什么上述程序中的后臺進程在main線程死亡之后后臺進程還進行到114的原因。
3.線程睡眠
如果需要線程停頓一段時間進入阻塞狀態,可以調用Thread類的靜態方法sleep(),sleep()有兩種重載形式<br>
- sleep(long millis)
讓當前正在執行的線程暫停mili毫秒,進入阻塞狀態。該方法受到系統計時器和線程調度器精度的影響。- sleep(long millis,int nanos)
讓當前正在執行的線程暫停milis毫秒+nanos微秒,進入阻塞狀態。該方法受到系統計時器和線程調度器精度的影響(不常用)
代碼演示:
public class SleepThread{
public static void main(String[] args) throws Exception {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
Thread.sleep(1000);
}
}
}
運行結果:
觀察程序想運行過程會發現,每一個進程之間相隔1秒。
4.線程讓步
調用Thrad類的靜態方法 yield()方法可以實現線程讓步,和sleep()方法類似,yield()方法也是讓當前正在運行的線程暫停,但是不會使線程阻塞,而是讓線程進入就緒狀態,讓線程調度器重新安排一次線程調度,完全有可能出現的狀況是,剛剛調用yield()方法進入就緒狀態的線程就被線程調度器重新調度出來重新執行。
代碼演示:
public class YieldThread extends Thread{
public YieldThread(String str) {
super(str);
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<60;i++) {
System.out.println(getName()+" "+i);
if(i==20){
Thread.yield(); //線程讓步
}
}
}
public static void main(String[] args) {
YieldThread yt1=new YieldThread("高級");
yt1.setPriority(MAX_PRIORITY); //將次線程設置成最高優先級
yt1.start(); //啟動線程
YieldThread yt2=new YieldThread("低級");
yt2.setPriority(MIN_PRIORITY);
yt2.start();
}
}
1.線程調用yield()方法后將執行的機會讓給優先級相同的線程<br>2.高優先級的線程調用yield()方法暫停之后,系統中沒有與之相同優先級和更高的優先級的線程,則線程調度器會將該線程重新調度出來,重新執行。
-
sleep()方法和yield()方法比較
方法 | 執行機會 | 線程狀態 | 是否拋出異常 | 可移植性 |
---|---|---|---|---|
sleep() | 線程暫停之后,會給其他線程執行的機會,不用理會線程的優先級 | 阻塞狀態 | InterruptedExextion異常 | 優 |
yield() | 線程暫停之后,只會給優先級相同或更高優先級的線程執行的機會 | 就緒狀態 | 否 | 差 |
5.改變線程優先級
1.一般線程具有優先級,更高優先級的線程比優先級低的線程能獲得更多的執行機會<br>2.每個線程默認的優先級和創建他們的父線程的優先級相同。<br> 3.Thread類提供了setPriorit(int newPriority)、getPriority() 方法來設置和獲取線程的優先級<br>4.也可以使用Thread類的3個靜態常量來設置線程的優先級:<br>
MAX_PRIORITY : 值是10<br> MIN_PRIORIT : 值是1<br> NORM_PRIORITY : 值是5
共同學習,寫下你的評論
評論加載中...
相關文章推薦