[TOC]
4 運(yùn)行原理
4.1 棧與棧幀
Java Virtual Machine Stacks (Java 虛擬機(jī)棧)
我們都知道 JVM 中由堆、棧、方法區(qū)所組成坤邪,其中棧內(nèi)存是給誰用的呢?其實(shí)就是線程,每個(gè)線程啟動(dòng)后萨醒,虛擬機(jī)就會(huì)為其分配一塊棧內(nèi)存。
- 每個(gè)棧由多個(gè)棧幀(Frame)組成苇倡,對(duì)應(yīng)著每次方法調(diào)用時(shí)所占用的內(nèi)存
- 每個(gè)線程只能有一個(gè)活動(dòng)棧幀富纸,對(duì)應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法
比如說囤踩,這里就有多個(gè)方法調(diào)用時(shí)的棧幀,每一個(gè)棧幀的右邊都有其自己對(duì)應(yīng)的變量和屬性晓褪。如果一個(gè)方法執(zhí)行完了堵漱,那么這個(gè)棧幀的內(nèi)存就會(huì)被回收,這個(gè)是不需要我們自己手動(dòng)操作的涣仿。
PS:我們調(diào)試的時(shí)候有一個(gè)小技巧勤庐,就是drop to frame,對(duì)應(yīng)圖片中的圖標(biāo)好港,他的意思是埃元,當(dāng)你點(diǎn)擊這個(gè)圖標(biāo),他會(huì)放棄當(dāng)前棧幀媚狰,并且返回上一個(gè)調(diào)用方法岛杀。
4.2 圖解運(yùn)行棧幀
java代碼
public class FrameTest {
public static void main(String[] args) {
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
- 首先一開始程序會(huì)程序棧、方法區(qū)和堆崭孤,方法區(qū)存儲(chǔ)的就是方法的內(nèi)容类嗤,main程序運(yùn)行的時(shí)候會(huì)有String數(shù)組,然后傳到main的局部變量表里面
- main方法執(zhí)行第一行method1(10)代碼辨宠,這個(gè)代碼指令會(huì)放到程序計(jì)數(shù)器里面(程序計(jì)數(shù)器記錄的就是當(dāng)前線程需要執(zhí)行的指令遗锣,如果CPU要執(zhí)行這個(gè)線程,其實(shí)就是從程序計(jì)數(shù)器里面拿執(zhí)行的指令)
當(dāng)執(zhí)行到了method1就會(huì)在程序棧里面開辟一個(gè)新的程序棧幀
當(dāng)執(zhí)行到Object m = method2()嗤形,這時(shí)又會(huì)開一個(gè)新的程序棧幀
當(dāng)執(zhí)行完了Object n =new Object()精偿,method2返回時(shí),method2的棧幀會(huì)被釋放了赋兵,同時(shí)method2棧幀里面的返回地址回到上一個(gè)方法笔咽,同時(shí)把m指向開辟的Object的堆內(nèi)存。
當(dāng)執(zhí)行完method1和main方法也是類似霹期。
4.3 多線程棧和棧幀
package com.bruce.test;
public class FrameTest {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
method2();
}
};
t1.start();
method1(30);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
我們直接看這個(gè)的運(yùn)行情況叶组,就可以看到有兩個(gè)線程是已經(jīng)停止了
可見,棧幀是以線程為單位历造,兩者相互獨(dú)立甩十,里面的變量是相互獨(dú)立的。
4.4 上下文切換
因?yàn)橐韵乱恍┰驅(qū)е翪PU不在執(zhí)行當(dāng)前的線程吭产,轉(zhuǎn)而執(zhí)行另一個(gè)線程的代碼:
- 線程的CPU時(shí)間片用完
- 垃圾回收
- 有更高優(yōu)先級(jí)的線程需要運(yùn)行
- 線程自己調(diào)用了sleep,yield,wait,join,park,synchronized,lock等方法
當(dāng)Context Switch發(fā)生時(shí)侣监,需要由操作系統(tǒng)保存當(dāng)前線程的狀態(tài),并恢復(fù)另一個(gè)線程的狀態(tài)臣淤,Java中對(duì)應(yīng)的概念就是程序計(jì)數(shù)器(Program counter Register)橄霉,它的作用就是記住下一條jvm指令的執(zhí)行地址,是線程私有的
- 狀態(tài)包括程序計(jì)數(shù)器荒典、虛擬機(jī)棧中的每個(gè)棧幀的信息酪劫,如局部變量吞鸭、操作數(shù)棧、返回地址等
- Context Switch頻繁發(fā)生會(huì)影響性能
4.4.1 圖解上下文切換
- 比如說由main線程切換到t1線程的時(shí)候覆糟,main線程里面的狀態(tài)信息都會(huì)保存起來
- CPU會(huì)執(zhí)行t1線程里面的程序計(jì)數(shù)器里面的指令刻剥。
5 線程的常用方法
方法名 | static | 功能說明 | 注意 |
---|---|---|---|
start | 啟動(dòng)一個(gè)新線程,在新的線程運(yùn)行run方法中的代碼 | start方法只是讓線程進(jìn)入就緒滩字,里面代碼不一定立刻運(yùn)行(CPU的時(shí)間片還沒分給它)造虏。每個(gè)線程對(duì)象的start方法智能調(diào)用一次,如果調(diào)用了多次會(huì)出現(xiàn)IllegalThreadStateException | |
run | 新線程啟動(dòng)后悔調(diào)用的方法 | 如果在構(gòu)造Thread對(duì)象時(shí)傳遞了Runnable參數(shù)麦箍,則線程啟動(dòng)后悔調(diào)用Runnable中的run方法漓藕,否則默認(rèn)不執(zhí)行任何操作。但可以創(chuàng)建Thread的子類對(duì)象來覆蓋默認(rèn)行為 | |
join | 等待線程運(yùn)行結(jié)束 | ||
join(long n) | 等待線程運(yùn)行結(jié)束挟裂,最多等待n毫秒 | ||
get() | 獲取線程長整形的id | id唯一 | |
getName() | 獲取線程名 | ||
setName(String) | 修改線程名 | ||
getPriority() | 獲取線程優(yōu)先級(jí) | ||
setPriority(int) | 修改線程優(yōu)先級(jí) | java中規(guī)定線程優(yōu)先級(jí)1~10的整數(shù)享钞,較大優(yōu)先級(jí)能提高該線程被CPU調(diào)度的概率 | |
getState(0) | 獲取線程狀態(tài) | Java中線程狀態(tài)是用6個(gè)enum表示,分別為NEW RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED | |
isInterrupted() | 判斷是否被打斷 | 不會(huì)清除打斷標(biāo)記 | |
isAlive() | 線程存活(還沒有運(yùn)行完畢) | ||
interrupt() | 打斷線程 | 如果被打斷線程正在sleep,wait,join會(huì)導(dǎo)致被打斷的線程拋出InterruptedException,并清除打斷標(biāo)記诀蓉;如果打斷的正在運(yùn)行的線程栗竖,則會(huì)設(shè)置打斷標(biāo)記;park的線程被打斷渠啤,也會(huì)設(shè)置打斷標(biāo)記 | |
interrupted() | static | 判斷當(dāng)前線程是否被打斷 | 會(huì)清除打斷標(biāo)記 |
currentThread(0) | static | 獲取點(diǎn)前正在執(zhí)行的線程 | |
sleep(long n) | static | 讓當(dāng)前執(zhí)行的線程休眠n毫秒狐肢,休眠時(shí)讓出cpu的時(shí)間片給其他線程 | |
yield() | static | 提示線程調(diào)度器讓出當(dāng)前線程對(duì)CPU的使用 | 主要是為了測試和調(diào)試 |