本篇博客主要介紹線程概念、線程的創(chuàng)建讯沈、線程狀態(tài)以及線程的常用方法凝果!
一祝迂、基本概念
程序(program): 是為完成特定任務(wù)、用某種語(yǔ)言編寫(xiě)的一組指令的集合器净。即指一 段靜態(tài)的代碼型雳,靜態(tài)對(duì)象。
進(jìn)程(process):是程序的一次執(zhí)行過(guò)程山害,或是正在運(yùn)行的一個(gè)程序纠俭。是一個(gè)動(dòng)態(tài) 的過(guò)程:有它自身的產(chǎn)生、存在和消亡的過(guò)程浪慌≡┚#——生命周期
運(yùn)行中的QQ,運(yùn)行中的MP3播放器
程序是靜態(tài)的权纤,進(jìn)程是動(dòng)態(tài)的
進(jìn)程作為資源分配的單位钓简,系統(tǒng)在運(yùn)行時(shí)會(huì)為每個(gè)進(jìn)程分配不同的內(nèi)存區(qū)域
線程(thread):進(jìn)程可進(jìn)一步細(xì)化為線程乌妒,是一個(gè)程序內(nèi)部的一條執(zhí)行路徑。
若一個(gè)進(jìn)程同一時(shí)間并行執(zhí)行多個(gè)線程涌庭,就是支持多線程的
線程作為調(diào)度和執(zhí)行的單位芥被,每個(gè)線程擁有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(pc),線程切換的開(kāi) 銷(xiāo)小
一個(gè)進(jìn)程中的多個(gè)線程共享相同的內(nèi)存單元/內(nèi)存地址空間->它們從同一堆中分配對(duì)象坐榆,可以 訪問(wèn)相同的變量和對(duì)象拴魄。這就使得線程間通信更簡(jiǎn)便、高效席镀。但多個(gè)線程操作共享的系統(tǒng)資 源可能就會(huì)帶來(lái)安全的隱患匹中。
單核CPU與多核CPU
單核CPU,其實(shí)是一種假的多線程豪诲,因?yàn)樵谝粋€(gè)時(shí)間單元內(nèi)顶捷,也只能執(zhí)行一個(gè)線程 的任務(wù)。例如:雖然有多車(chē)道屎篱,但是收費(fèi)站只有一個(gè)工作人員在收費(fèi)服赎,只有收了費(fèi) 才能通過(guò),那么CPU就好比收費(fèi)人員交播。如果有某個(gè)人不想交錢(qián)重虑,那么收費(fèi)人員可以 把他“掛起”(晾著他,等他想通了秦士,準(zhǔn)備好了錢(qián)缺厉,再去收費(fèi))。但是因?yàn)镃PU時(shí) 間單元特別短隧土,因此感覺(jué)不出來(lái)提针。
如果是多核的話,才能更好的發(fā)揮多線程的效率曹傀。(現(xiàn)在的服務(wù)器都是多核的)
一個(gè)Java應(yīng)用程序java.exe辐脖,其實(shí)至少有三個(gè)線程:main()主線程,gc() 垃圾回收線程皆愉,異常處理線程揖曾。當(dāng)然如果發(fā)生異常,會(huì)影響主線程亥啦。
并行與并發(fā)
并行:多個(gè)CPU同時(shí)執(zhí)行多個(gè)任務(wù)。比如:多個(gè)人同時(shí)做不同的事
并發(fā):一個(gè)CPU(采用時(shí)間片)同時(shí)執(zhí)行多個(gè)任務(wù)练链。比如:秒殺翔脱、多個(gè)人做同一件事
二、線程的創(chuàng)建(三種方式)
1.繼承Thread類(lèi)(重點(diǎn))
- 自定義線程類(lèi)繼承Thread類(lèi)
- 重寫(xiě)Thread類(lèi)的run方法
- 創(chuàng)建自定義線程類(lèi)對(duì)象媒鼓,調(diào)用start()方法
// 創(chuàng)建線程方式一:繼承Thread類(lèi)届吁,實(shí)現(xiàn)run()方法错妖, 調(diào)用start()方法開(kāi)啟線程
// 總結(jié):線程開(kāi)啟之后不一定立即執(zhí)行,由CPU調(diào)度執(zhí)行
public class TestThread1 extends Thread {
@Override
public void run() {
// run方法:線程體
for (int i = 0; i < 20; i++) {
System.out.println("我在看代碼"+i);
}
}
public static void main(String[] args) {
// 創(chuàng)建一個(gè)線程對(duì)象
TestThread1 testThread1 = new TestThread1();
// 調(diào)用start方法
testThread1.start();
// 主方法:main線程
for (int i = 0; i < 200; i++) {
System.out.println("我在學(xué)習(xí)多線程"+i);
}
}
}
2.實(shí)現(xiàn)Runnable接口(重點(diǎn))
1.定義一個(gè)類(lèi)實(shí)現(xiàn)Runnable接口
2.實(shí)現(xiàn)Runnable接口的run()方法
3.新建該類(lèi)的線程類(lèi)對(duì)象疚沐,調(diào)用start()方法
// 創(chuàng)建線程方式二:實(shí)現(xiàn)runnable接口; 重寫(xiě)run()方法暂氯;創(chuàng)建Thread對(duì)象(需要丟入Runnable接口實(shí)現(xiàn)類(lèi)),調(diào)用start()方法開(kāi)啟線程
public class TestThread3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代碼" + i);
}
}
public static void main(String[] args) {
// 創(chuàng)建一個(gè)Runnable接口實(shí)現(xiàn)類(lèi)的對(duì)象
TestThread3 thread3 = new TestThread3();
// 創(chuàng)建一個(gè)線程對(duì)象亮蛔,通過(guò)線程對(duì)象來(lái)開(kāi)啟我們的線程痴施,代理
Thread thread = new Thread(thread3);
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在學(xué)習(xí)多線程" + i);
}
}
}
3.繼承Callable接口(了解)
1.創(chuàng)建一個(gè)類(lèi)實(shí)現(xiàn)Callable接口
2.實(shí)現(xiàn)Callable接口的call方法
3.創(chuàng)建實(shí)現(xiàn)Callable接口的類(lèi)對(duì)象
4.創(chuàng)建執(zhí)行服務(wù)ExecutorService service = Executors.newFixedThreadPool(線程數(shù))
5.提交使得線程進(jìn)入就緒狀態(tài)Future<> r1 = service .submit(實(shí)現(xiàn)Callable接口的類(lèi))
6.獲取提交結(jié)果 rs1 = r1.get()
7.關(guān)閉服務(wù)service.shutdown()
// 創(chuàng)建線程方式三:實(shí)現(xiàn)Callable接口
public class TestCallable implements Callable<Boolean> {
private String name;
private String url;
public TestCallable(String url, String name) {
this.name = name;
this.url = url;
}
@Override
public Boolean call() throws Exception {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoad(this.url, this.name);
System.out.println(this.name + "下載完成!");
return true;
}
public static void main(String[] args) throws Exception {
TestCallable t1 = new TestCallable("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2800189401,4260987560&fm=26&gp=0.jpg", "1.jpg");
TestCallable t2 = new TestCallable("https://ns-strategy.cdn.bcebos.com/ns-strategy/upload/fc_big_pic/part-00527-1400.jpg", "2.jpg");
TestCallable t3 = new TestCallable("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=760070108,1900073437&fm=26&gp=0.jpg", "3.jpg");
// 創(chuàng)建執(zhí)行服務(wù)
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交執(zhí)行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
// 獲取結(jié)果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
// 關(guān)閉服務(wù)
ser.shutdown();
}
}
class WebDownLoader {
public void downLoad(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、線程狀態(tài)
線程狀態(tài)圖:
下面我們結(jié)合這張圖來(lái)介紹線程的幾個(gè)狀態(tài)以及幾個(gè)常用指令
1.線程的停止
jdk雖然提供了兩個(gè)線程停止的方法stop()
與destory()
,但是JDK并不建議我們這樣做究流,在實(shí)際的操縱過(guò)程中辣吃,我們一般讓線程正常停止或者設(shè)置標(biāo)志位來(lái)使得線程停止
// 測(cè)試停止
// 1.建議線程正常停止 ---> 利用次數(shù),不建議使用死循環(huán)
// 2.建議使用標(biāo)志位 ---> 設(shè)置一個(gè)標(biāo)志位
// 3.不要使用stop或者destory等過(guò)時(shí)的方法或者jdk不建議使用的方法
public class TestStop implements Runnable {
// 線程停止標(biāo)志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while(this.flag) {
System.out.println(Thread.currentThread().getName() + ":" + i ++ );
}
}
// 停止線程方法
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
Thread thread = new Thread(testStop, "子線程1");
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
if(i == 900) {
testStop.stop();
System.out.println("子線程停止了芬探!");
}
}
}
}
2.線程休眠
通過(guò)調(diào)用Thread.sleep(休眠毫秒數(shù))
方法神得,就可實(shí)現(xiàn)線程的休眠,線程進(jìn)入阻塞狀態(tài)偷仿。
線程休眠方法可以模擬計(jì)時(shí)以及模擬網(wǎng)絡(luò)延時(shí)的問(wèn)題
注意:線程進(jìn)入阻塞狀態(tài)之后哩簿,不會(huì)釋放鎖!
// 模擬倒計(jì)時(shí)
public class TestSleep2 {
public static void tenDown() throws InterruptedException {
int num = 10;
while(num > 0) {
Thread.sleep(1000);
System.out.println(num--);
}
}
public static void main(String[] args) throws InterruptedException {
tenDown();
}
}
3.線程禮讓?zhuān)▂ield)
- 讓當(dāng)前正在執(zhí)行的線程暫停但不阻塞
- 將線程從運(yùn)行狀態(tài)變?yōu)榫途w狀態(tài)
- 讓CPU重新調(diào)度酝静,禮讓不一定成功节榜,看CPU心情
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "線程開(kāi)始!");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "線程結(jié)束形入!");
}
}
多次運(yùn)行結(jié)果如下:
從運(yùn)行結(jié)果可以看出全跨,每次運(yùn)行結(jié)果都不一樣,禮讓不一定成功亿遂,需要看CPU的調(diào)度浓若!
4.線程合并(join,這里我認(rèn)為翻譯為線程插隊(duì)更合適)
- join合并線程,待此線程執(zhí)行完畢之后再執(zhí)行其他線程蛇数,該線程在執(zhí)行過(guò)程中使得其他線程阻塞挪钓!
- 可以想象成插隊(duì)
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("線程VIP來(lái)了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
if(i==50) {
thread.join(); // 線程來(lái)插隊(duì)了
}
}
}
}
5. 線程狀態(tài)檢測(cè)
jdk文檔中線程包含如下六種狀態(tài):
調(diào)用線程對(duì)象的getState()
方法即可獲得線程狀態(tài)!
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("http://////");
});
Thread.State state = thread.getState();
thread.start();
while(Thread.State.TERMINATED != state) {
System.out.println(state);
try {
Thread.sleep(1000);
state = thread.getState();
System.out.println(state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6.線程優(yōu)先級(jí)(Priority)
- 通過(guò)調(diào)用線程對(duì)象的
setPriority()
方法實(shí)現(xiàn) - 如果要設(shè)置線程優(yōu)先級(jí)耳舅,在調(diào)用
start()
方法之前設(shè)置優(yōu)先級(jí) - 線程的優(yōu)先級(jí)大于等于10小于等于1
- 線程的優(yōu)先級(jí)高不一定就先運(yùn)行碌上,運(yùn)行順序具體還要看CPU的調(diào)度
public class TestPriority extends Thread {
public static void main(String[] args) {
// 查看Main函數(shù)的優(yōu)先級(jí)
System.out.println("main--->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority, "線程1");
Thread t2 = new Thread(myPriority, "線程2");
Thread t3 = new Thread(myPriority, "線程3");
Thread t4 = new Thread(myPriority, "線程4");
Thread t5 = new Thread(myPriority, "線程5");
Thread t6 = new Thread(myPriority, "線程6");
t1.start();
// 如果要設(shè)置優(yōu)先級(jí),先設(shè)置優(yōu)先級(jí)浦徊,再啟動(dòng)
// 線程的優(yōu)先級(jí)值大于等于1小于等于10
t2.setPriority(Thread.MIN_PRIORITY); // MIN_PRIORITY = 1
t2.start();
t3.setPriority(Thread.MAX_PRIORITY); // MAX_PRIORITY = 10
t3.start();
t4.setPriority(4);
t4.start();
t5.setPriority(5);
t5.start();
t6.setPriority(6);
t6.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "----->" + Thread.currentThread().getPriority());
}
}
7. 守護(hù)線程(daemon)
- 線程分為 用戶(hù)線程與守護(hù)線程
- java虛擬機(jī)必須保證用戶(hù)線程執(zhí)行完畢(比如我們的main線程)
- 虛擬機(jī)不用等待守護(hù)線程執(zhí)行完畢(后臺(tái)記錄操作日志馏予,監(jiān)控內(nèi)存,垃圾回收盔性。霞丧。。都是守護(hù)線程)
- 程序停止了冕香,守護(hù)線程也就停止了
- 調(diào)用線程對(duì)象的
setDaemon(true)
設(shè)置線程為守護(hù)線程
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Thread gt = new Thread(god);
gt.setDaemon(true); // 將該線程設(shè)置為守護(hù)線程蛹尝,默認(rèn)為flase
gt.start();
new Thread(new You()).start(); // 啟動(dòng)一般線程
}
}
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("你開(kāi)心的活著后豫!" + i);
}
System.out.println("=======goodbye world!");
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝守護(hù)著你!");
}
}
}