java基础线程_Java基础之多线程没那么复杂!
多線程的引入
1.什么是多線程
線程是程序執(zhí)行的一條路徑,一個(gè)進(jìn)程中可以包含多條線程;多線程并發(fā)執(zhí)行可以提高程序的效率
2.進(jìn)程和線程之間的關(guān)系
操作系統(tǒng)可以同時(shí)執(zhí)行多個(gè)任務(wù),每個(gè)任務(wù)就是進(jìn)程;進(jìn)程可以同時(shí)執(zhí)行多個(gè)任務(wù),每個(gè)任務(wù)就是線程。
3.多線程的應(yīng)用場景
紅蜘蛛同時(shí)共享屏幕給多個(gè)電腦
迅雷開啟多條線程一起下載
QQ開啟多人聊天
服務(wù)器同時(shí)處理多個(gè)客戶的請求
多線程并行和并發(fā)的區(qū)別
并行性和并發(fā)性是兩個(gè)概念,并行性指在同一時(shí)刻,有多條指令在多個(gè)處理器上同時(shí)執(zhí)行
并發(fā)性指的是同一時(shí)刻只有一條指令被執(zhí)行,但多個(gè)進(jìn)程指令被快速切換執(zhí)行是的在宏觀上具有多個(gè)進(jìn)程被同時(shí)執(zhí)行的效果
Java程序運(yùn)行原理和JVM的啟動(dòng)是多線程的嗎?
java程序運(yùn)行原理
Java命令啟動(dòng)jvm,啟動(dòng)jvm等于啟動(dòng)一個(gè)應(yīng)用程序,也就是啟動(dòng)了一個(gè)進(jìn)程,該進(jìn)程會(huì)自動(dòng)啟動(dòng)一個(gè)“主線程”,然后主線程去調(diào)用某個(gè)類的main方法
Jvm啟動(dòng)是多線程的的嗎
JVM啟動(dòng)至少啟動(dòng)了垃圾回收線程和主線程,所以是多線程
多線程的實(shí)現(xiàn)方式
1. 繼承Thread類創(chuàng)建線程類
(1) 定義Thread的子類,并重寫該類的run方法,該ru的執(zhí)行體就代表了線程需要完成的任務(wù),因此run()方法被稱為線程執(zhí)行體
(2) 創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對(duì)象
(3) 調(diào)用線程對(duì)象的start()方法啟動(dòng)該線程
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方法");
}
}
}
上述代碼的執(zhí)行驗(yàn)證了多線程,如果上述程序的執(zhí)行過程是多線程的話,會(huì)發(fā)現(xiàn)屏幕中的 ”我是主方法”和“我是run方法” 的字樣是交替出現(xiàn)的,這說明了程序的的執(zhí)行過程為并行執(zhí)行Thread類的Start()方法啟動(dòng)run()方法的線程,和主方法中的執(zhí)行同時(shí)進(jìn)行。
2.實(shí)現(xiàn)Runnable接口創(chuàng)建線程類
(1) 定義一個(gè)實(shí)現(xiàn)了Runnable接口的實(shí)現(xiàn)類
(2) 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例
(3) 將創(chuàng)建的實(shí)例作為Thread類的target類創(chuàng)建Thread對(duì)象,該對(duì)象才是真正的線程對(duì)象
(4) 用創(chuàng)建的Thread對(duì)象啟動(dòng)線程
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();
}
}
}
}
查看API文檔,會(huì)發(fā)現(xiàn)Runnable接口只定義了run()方法這一個(gè)抽象類,所以實(shí)現(xiàn)Runnable接口的實(shí)現(xiàn)類只有run()方法,僅作為線程執(zhí)行體,所以,Runnable對(duì)象僅僅作為Thread對(duì)象的target,而實(shí)際的線程對(duì)象依然是Thread實(shí)例,Thread實(shí)例負(fù)責(zé)執(zhí)行target的run()方法。
3.實(shí)現(xiàn)Callable接口和Future接口創(chuàng)建多線程
(1)Callable接口更像是Runnable接口的增強(qiáng)版,相比較Runable接口,Call()方法新增捕獲和拋出異常的功能;Call()方法可以返回值
(2)Future接口提供了一個(gè)實(shí)現(xiàn)類FutureTask實(shí)現(xiàn)類,FutureTaks類用來保存Call()方法的返回值,并作為Thread類的target。
(3)調(diào)用FutureTask的get()方法來獲取返回值
package Demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* (1)創(chuàng)建Callable接口的實(shí)現(xiàn)類,并重寫call()方法,該call()方法作為線程的執(zhí)行體,且有返回值
* (2)創(chuàng)建了Callable接口的實(shí)現(xiàn)類的實(shí)例,并用FutureTask()方法包裝對(duì)象,該FutureTask()對(duì)象實(shí)現(xiàn)了
* 將對(duì)象的返回值包裝的功能
* (3)使用FutureTask對(duì)象將Thread對(duì)象的target,創(chuàng)建并啟動(dòng)線程
* (4)調(diào)用FutureTask對(duì)象的get()方法獲得子線程執(zhí)行結(jié)束后的返回值
* */
public class Callable_Future implements Callable {
@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 {
//創(chuàng)建Callable的對(duì)象
Callable_Future ca=new Callable_Future();
FutureTask ft=new FutureTask(ca);
for(int i=0;i<100;i++) {
//返回值主線程的名稱和執(zhí)行代號(hào)
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new Thread(ft,"Callable線程").start();
//該方法將導(dǎo)致主線程被阻塞,直到call()方法結(jié)束并返回為止
//System.out.println("子線程的返回值"+ft.get());
}
}
try{
System.out.println("子線程的返回值"+ft.get());
}catch (Exception e) {
e.printStackTrace();
}
}
}
上面的程序中,FutufeTask方法的get()方法將獲得Call()方法的返回值,但是該方法將導(dǎo)致主線程受阻直到Call()方法結(jié)束并返回為止。
4.三種實(shí)現(xiàn)多線程的方式對(duì)比
實(shí)現(xiàn)方式
重寫方法
啟動(dòng)線程的方式
優(yōu)點(diǎn)
缺點(diǎn)
繼承Thread類
run()
Thread對(duì)象.start()
編程簡單,直接用this即可獲取當(dāng)前對(duì)象
繼承Thread的子類不能繼承其他的類
實(shí)現(xiàn)Runnable接口
run()
Thread對(duì)象.start()
1.實(shí)現(xiàn)類可以繼承其他類
2.多個(gè)對(duì)象共享一個(gè)target,形成了清晰的模型
編程復(fù)雜,必須使用Thread.currentThread()返回當(dāng)前對(duì)象
實(shí)現(xiàn)Callable接口
Call()
Thread對(duì)象.start()
1.同Runnable接口
2.Call()方法有返回值并能拋出異常
同Runnable接口
通過上面的對(duì)比發(fā)現(xiàn),一般在項(xiàng)目中,我們使用Runnable接或者Callable接口來實(shí)現(xiàn)多線程。
5.線程的生命周期
線程并不是創(chuàng)建過之后就開始執(zhí)行的,也不是一直處于執(zhí)行狀態(tài),一個(gè)線程的生命周期一個(gè)又五個(gè):新建、就緒、運(yùn)行、阻塞、死亡。
2.線程的這五種狀態(tài)的原理參照《計(jì)算機(jī)組成原理2》多線程部分
6.控制線程
1.join線程
1.Thread類中的成員方法,讓調(diào)用join()方法的線程處于等待狀態(tài),直到子線程執(zhí)行結(jié)束
2.join()方法通常由使用線程的程序調(diào)用,用于將一個(gè)線程拆分成若干小的線程執(zhí)行,執(zhí)行結(jié)束最后由主線程進(jìn)行進(jìn)一步的操作
代碼演示:
public class joinThread extends Thread {
//創(chuàng)建一個(gè)有參構(gòu)造函數(shù),用來為線程命名
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 {
//啟動(dòng)子線程
new joinThread("子線程").start();
for(int i=0;i<100;i++) {
if(i==20) {
joinThread jt=new joinThread("新線程的子線程");
jt.start(); //啟動(dòng)子線程
jt.join(); //讓主線程處于等待狀態(tài)
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
mian線程調(diào)用join之后,出去阻塞狀態(tài),兩個(gè)子線程并發(fā)執(zhí)行。直到兩個(gè)子程序執(zhí)行結(jié)束之后,main線程才開始執(zhí)行。
join()線程有如下三種重載形式
1.join() :
2.join(long millis) :被join的線程的時(shí)間最長為millis毫秒,如果超過了這個(gè)millis則線程不被執(zhí)行
3.join(long millis,int nanos) :被join的線程的時(shí)間最長為millis毫秒+nanos微秒,如果超過了這個(gè)時(shí)間則線程不被執(zhí)行
第三種重載行駛一般用不到,因?yàn)闊o論是計(jì)算機(jī)系統(tǒng)還是計(jì)算機(jī)硬件,還沒發(fā)精確到微秒
2.后臺(tái)線程
后臺(tái)線程運(yùn)行于后臺(tái),任務(wù)是為其他線程提供服務(wù),也被稱為“守護(hù)線程”或“精靈線程”。JVM的垃圾回收機(jī)制就是一個(gè)典型的后臺(tái)線程。
通過調(diào)用Thread類的setDaemon(true)方法將線程設(shè)置為后臺(tái)線程
代碼演示:
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);
}
}
}
運(yùn)行結(jié)果:
1.Thread-0的后臺(tái)進(jìn)程本該當(dāng)i=999的時(shí)候才停止進(jìn)行,但是程序中只進(jìn)行到114次,這是因?yàn)樗械那芭_(tái)進(jìn)程結(jié)束之后,后天進(jìn)程的存在也就是去了意義,所以后臺(tái)進(jìn)程也跟著死亡。
2.前臺(tái)線程死亡之后,JVM會(huì)通知后臺(tái)線程死亡,但是從后臺(tái)線程接收指令到做出反應(yīng)需要一定的時(shí)間,這就是為什么上述程序中的后臺(tái)進(jìn)程在main線程死亡之后后臺(tái)進(jìn)程還進(jìn)行到114的原因。
3.線程睡眠
如果需要線程停頓一段時(shí)間進(jìn)入阻塞狀態(tài),可以調(diào)用Thread類的靜態(tài)方法sleep(),sleep()有兩種重載形式
sleep(long millis)
讓當(dāng)前正在執(zhí)行的線程暫停mili毫秒,進(jìn)入阻塞狀態(tài)。該方法受到系統(tǒng)計(jì)時(shí)器和線程調(diào)度器精度的影響。
sleep(long millis,int nanos)
讓當(dāng)前正在執(zhí)行的線程暫停milis毫秒+nanos微秒,進(jìn)入阻塞狀態(tài)。該方法受到系統(tǒng)計(jì)時(shí)器和線程調(diào)度器精度的影響(不常用)
代碼演示:
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);
}
}
}
運(yùn)行結(jié)果:
觀察程序想運(yùn)行過程會(huì)發(fā)現(xiàn),每一個(gè)進(jìn)程之間相隔1秒。
4.線程讓步
調(diào)用Thrad類的靜態(tài)方法 yield()方法可以實(shí)現(xiàn)線程讓步,和sleep()方法類似,yield()方法也是讓當(dāng)前正在運(yùn)行的線程暫停,但是不會(huì)使線程阻塞,而是讓線程進(jìn)入就緒狀態(tài),讓線程調(diào)度器重新安排一次線程調(diào)度,完全有可能出現(xiàn)的狀況是,剛剛調(diào)用yield()方法進(jìn)入就緒狀態(tài)的線程就被線程調(diào)度器重新調(diào)度出來重新執(zhí)行。
代碼演示:
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("高級(jí)");
yt1.setPriority(MAX_PRIORITY); //將次線程設(shè)置成最高優(yōu)先級(jí)
yt1.start(); //啟動(dòng)線程
YieldThread yt2=new YieldThread("低級(jí)");
yt2.setPriority(MIN_PRIORITY);
yt2.start();
}
}
1.線程調(diào)用yield()方法后將執(zhí)行的機(jī)會(huì)讓給優(yōu)先級(jí)相同的線程
2.高優(yōu)先級(jí)的線程調(diào)用yield()方法暫停之后,系統(tǒng)中沒有與之相同優(yōu)先級(jí)和更高的優(yōu)先級(jí)的線程,則線程調(diào)度器會(huì)將該線程重新調(diào)度出來,重新執(zhí)行。
sleep()方法和yield()方法比較
方法
執(zhí)行機(jī)會(huì)
線程狀態(tài)
是否拋出異常
可移植性
sleep()
線程暫停之后,會(huì)給其他線程執(zhí)行的機(jī)會(huì),不用理會(huì)線程的優(yōu)先級(jí)
阻塞狀態(tài)
InterruptedExextion異常
優(yōu)
yield()
線程暫停之后,只會(huì)給優(yōu)先級(jí)相同或更高優(yōu)先級(jí)的線程執(zhí)行的機(jī)會(huì)
就緒狀態(tài)
否
差
5.改變線程優(yōu)先級(jí)
1.一般線程具有優(yōu)先級(jí),更高優(yōu)先級(jí)的線程比優(yōu)先級(jí)低的線程能獲得更多的執(zhí)行機(jī)會(huì)
2.每個(gè)線程默認(rèn)的優(yōu)先級(jí)和創(chuàng)建他們的父線程的優(yōu)先級(jí)相同。
3.Thread類提供了setPriorit(int newPriority)、getPriority() 方法來設(shè)置和獲取線程的優(yōu)先級(jí)
4.也可以使用Thread類的3個(gè)靜態(tài)常量來設(shè)置線程的優(yōu)先級(jí):
MAX_PRIORITY : 值是10
MIN_PRIORIT : 值是1
NORM_PRIORITY : 值是5
總結(jié)
以上是生活随笔為你收集整理的java基础线程_Java基础之多线程没那么复杂!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中的数组_Java中的数组
- 下一篇: 伤心总是难免的是哪首歌啊?