Java線程的概念
Java內(nèi)置支持多線程編程(multithreaded programming),多線程程序包含2條或者2條以上并發(fā)運(yùn)行的部分搔弄,程序中每個(gè)這樣的部分叫做線程(thread),
每個(gè)線程都有獨(dú)立的運(yùn)行路徑幅虑。因此多線程任務(wù)是多任務(wù)處理的一種特殊形式。
進(jìn)程和線程的比較
- 進(jìn)程:程序執(zhí)行時(shí)所占用的所有的資源的總稱或者說是容器
-
線程:是基本執(zhí)行單元
image
簡單總結(jié): 進(jìn)程是線程的容器顾犹。
進(jìn)程中的主線程
主線程的重要性體現(xiàn)在:
- 他是產(chǎn)生其他子線程的線程
- 通常它必須是最后完成執(zhí)行倒庵,因?yàn)樗鼘?zhí)行各種關(guān)閉操作
主線程是運(yùn)行程序時(shí)候自動(dòng)創(chuàng)建的,但是其也是一個(gè)Thread類的實(shí)例對(duì)象
一般由主線程創(chuàng)建其他子線程炫刷,然后由主線程慣例其他線程哄芜。
關(guān)于Thread類的基本使用
Java的多線程系統(tǒng)建立于Thread類和Runable接口. Thread類定義了好幾種方法來幫助管理線程:
方法 | 意義 |
---|---|
getName | 獲取線程名稱 |
getPriority | 獲取線程優(yōu)先級(jí) |
isAlive | 判定線程是否仍在執(zhí)行 |
join | 等待一個(gè)線程終止 |
run | 線程的入口 |
sleep | 在一定時(shí)間內(nèi)掛起線程 |
start | 通過調(diào)用運(yùn)行方法來啟動(dòng)線程 |
繼承Thread類創(chuàng)建新的線程
- 我們可以通過繼承Thread類,Override run()方法柬唯,然后創(chuàng)建該類的實(shí)例來創(chuàng)建除了主線程之外的其他線程认臊,并且在run()方法中賦予屬于這個(gè)線程的代碼邏輯,這個(gè)run()方法是新線程的入口锄奢。
- 這個(gè)新建的Thread的子類失晴,會(huì)繼承Thread類的一個(gè)start()方法,調(diào)用start()方法時(shí)拘央,會(huì)自動(dòng)生成一個(gè)線程涂屁,然后調(diào)用run()方法執(zhí)行該線程的邏輯。
package com.DeeJay.ThreadDemo;
public class ThreadDemo extends Thread{ // 自定義線程類
// 運(yùn)行的代碼邏輯和主線程不同
public ThreadDemo() {
// 創(chuàng)建一個(gè)新的子線程
super("Child Thread");
// 顯式的開始執(zhí)行這個(gè)線程 ===> 做一些基本的初始化操作 ===> run()
this.start();
}
// 新的子線程的入口灰伟!
@Override
public void run() {
try {
for (int i = 0; i < 10; i ++) {
System.out.println("Child Thread: " + i);
Thread.sleep(1000);
}
}catch (Exception e) {
System.out.println("Child Thread Error!");
}
}
}
package com.DeeJay;
import com.DeeJay.ThreadDemo.ThreadDemo;
public class Main {
public static void main(String[] args) {
// 創(chuàng)建新的子線程拆又!
ThreadDemo t = new ThreadDemo();
// 需要注意的是此處代碼執(zhí)行并不會(huì)像單線程一樣阻塞,而是會(huì)立即執(zhí)行下面語句
// 調(diào)用完構(gòu)造函數(shù)和start()之后栏账, 子線程開始執(zhí)行帖族,主線程返回到Main(),開始自己的邏輯
// 主線程邏輯
try {
for (int i = 0; i < 5; i ++) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
}catch (Exception e) {
System.out.println("Main Thread Error!");
}
}
}
此處在定義新的線程類的時(shí)候,也可以不在構(gòu)造函數(shù)內(nèi)部直接執(zhí)行start(),可以等新的線程類的實(shí)例對(duì)象創(chuàng)建后手動(dòng)執(zhí)行start()開始子線程邏輯挡爵。
實(shí)現(xiàn)Runable接口來創(chuàng)建新的線程
package com.DeeJay.ThreadDemo;
public class ThreadDemo implements Runnable{ // 改為實(shí)現(xiàn)Runnable接口
private Thread t;
public ThreadDemo(String name) {
// 選用帶target的構(gòu)造函數(shù)竖般, 將this傳過去,本質(zhì)上就是把run()方法傳遞過去
t = new Thread(this, name);
t.start();
}
// 一樣的 實(shí)現(xiàn)run() 作為線程的入口
@Override
public void run() {
for (int i = 0; i < 5; i ++) {
System.out.println("Runnable Thread: " + i);
}
System.out.println(t.getName() + " existing");
}
}
package com.DeeJay;
import com.DeeJay.ThreadDemo.ThreadDemo;
public class Main {
public static void main(String[] args) {
new ThreadDemo("Child Thread");
}
}
線程同步
當(dāng)多個(gè)線程需要共享資源茶鹃,他們就需要某種方法來確定資源在某一刻僅被一個(gè)線程占用著涣雕。
達(dá)到上述目的的過程就叫做同步(synchronization)
synchronized方法的使用
當(dāng)一個(gè)線程在一個(gè)synchronized方法的內(nèi)部艰亮,所有試圖調(diào)用該方法(或者其他synchronized方法)的同一個(gè)實(shí)例對(duì)象的線程必須等待。為了退出管程挣郭,并釋放對(duì)對(duì)象的控制權(quán)給其他等待的線程迄埃,擁有管程的線程僅需從synchronized方法返return即可。
一般如果一個(gè)對(duì)象的方法可能被多線程使用兑障,則用synchronized關(guān)鍵字修飾
來看一個(gè)具體的使用synchronized方法的例子:
Callme.java
package com.DeeJay;
public class Callme {
public void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
System.out.print("]");
}
}
Caller.java
package com.DeeJay;
public class Caller implements Runnable{
private String msg;
Thread t;
Callme target;
public Caller(String msg, Callme target) {
this.msg = msg;
this.t = new Thread(this);
this.target = target;
t.start();
}
@Override
public void run() {
target.call(this.msg);
}
}
Main
package com.DeeJay;
public class Main {
public static void main(String[] args) {
Callme target = new Callme(); // 共享的資源
Caller c1 = new Caller("hello", target);
Caller c2 = new Caller("world", target);
Caller c3 = new Caller("synchronized", target);
try {
c1.t.join();
c2.t.join();
c3.t.join();
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("\n");
System.out.println("Main Thread End!");
}
}
上述例子中侄非,主線程中new出來的target即為幾個(gè)子線程公用的資源。當(dāng)調(diào)用三個(gè)子線程時(shí)旺垒,三個(gè)子線程會(huì)并發(fā)執(zhí)行彩库,并不會(huì)等到一個(gè)執(zhí)行完再執(zhí)行下一個(gè)肤无,所以看到的輸出結(jié)果為:
[hello[world[synchronized]]]
我們想達(dá)到的效果是在一個(gè)子線程占用公用資源target時(shí)先蒋,不允許其他線程也使用該資源,為了達(dá)到這個(gè)效果宛渐,就可以給將target的這個(gè)call方法用synchronized
修飾:
Callme.java
package com.DeeJay;
public class Callme {
public synchronized void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
System.out.print("]");
}
}
之后再運(yùn)行竞漾,可以看到輸出的為:
[hello][synchronized][world]
synchronized修飾語句塊
對(duì)于上述的例子,還可以使用synchronized
修飾代碼塊來達(dá)到相同的效果窥翩,synchronized修飾的代碼塊就是同步執(zhí)行的业岁,我們可以不用在Callme類中修改call方法為synchronized,而是在調(diào)用Callme實(shí)例對(duì)象的Caller類中寇蚊,將代碼塊置為synchronized笔时,這樣代碼塊中的邏輯也達(dá)到了同步的效果。
Caller.java
package com.DeeJay;
public class Caller implements Runnable{
private String msg;
Thread t;
Callme target;
public Caller(String msg, Callme target) {
this.msg = msg;
this.t = new Thread(this);
this.target = target;
t.start();
}
@Override
public void run() {
synchronized (target) { // synchronized block
target.call(this.msg);
}
}
}
關(guān)于join
關(guān)于join()方法仗岸,join方法的作用是允耿,當(dāng)我們調(diào)用某個(gè)線程的這個(gè)方法時(shí),這個(gè)方法會(huì)掛起調(diào)用線程扒怖,直到被調(diào)用線程結(jié)束執(zhí)行较锡,調(diào)用線程才會(huì)繼續(xù)執(zhí)行。
關(guān)鍵是理解掛起調(diào)用線程盗痒,首先舉個(gè)例子:
子線程:
package com.DeeJay.ThreadDemo;
public class ThreadDemo extends Thread{
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("Child Thread End!");
}
}
Main:
package com.DeeJay;
import com.DeeJay.ThreadDemo.ThreadDemo;
public class Main {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
t.start();
try {
t.join(); //
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("Main Thread End!");
}
}
子線程的邏輯是等待1秒后輸出信息蚂蕴,當(dāng)調(diào)用join()后,子線程的調(diào)用線程此時(shí)是主線程俯邓,所以主線程會(huì)暫時(shí)掛起不繼續(xù)執(zhí)行骡楼,等待子線程執(zhí)行完成后,才會(huì)繼續(xù)執(zhí)行主線程稽鞭。這時(shí)候才輸出主線程中的信息君编。所以此時(shí)輸出信息應(yīng)該為:
// 一秒后輸出:
Child Thread End!
Main Thread End!
但是如果不調(diào)用子線程的join(),此時(shí)主線程并不會(huì)掛起川慌,會(huì)和子線程并發(fā)執(zhí)行吃嘿,主線程并沒有等待祠乃,所以會(huì)先于子線程執(zhí)行:
子線程:
package com.DeeJay;
import com.DeeJay.ThreadDemo.ThreadDemo;
public class Main {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
t.start();
try {
// t.join(); // 不調(diào)用join 在子線程執(zhí)行的過程中 主線程并不會(huì)被掛起
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("Main Thread End!");
}
}
此時(shí)輸出為:
Main Thread End! // 馬上輸出
Child Thread End! // 過一秒后輸出
當(dāng)有多個(gè)子線程時(shí),如果前面的子線程先于后面的子線程調(diào)用start()之前調(diào)用join()的話兑燥,此時(shí)調(diào)用線程還是主線程亮瓷,主線程依然會(huì)掛起,所以后面子線程的start()的邏輯要等到前面子線程執(zhí)行完后主線程繼續(xù)開始執(zhí)行才能執(zhí)行到降瞳。所以會(huì)有單線程的表現(xiàn)形式嘱支,但是此時(shí)其實(shí)仍是多線程。
package com.DeeJay;
import com.DeeJay.ThreadDemo.ThreadDemo;
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadDemo t1 = new ThreadDemo();
ThreadDemo t2 = new ThreadDemo();
t1.start();
t1.join(); // 此處調(diào)用t1的join() 會(huì)掛起主線程 t1執(zhí)行結(jié)束后才喚醒主線程繼續(xù)執(zhí)行t2.start()
t2.start();
t2.join(); // 同理 t2開始執(zhí)行時(shí)掛起主線程 t2執(zhí)行完后喚醒主線程
System.out.println("Main Thread End!");
}
}
輸出結(jié)果為:
Child Thread End! // 過1秒
Child Thread End! // 過2秒
Main Thread End! // 過2秒
線程之間的通信
以下方法用來和其他線程進(jìn)行交流, 并且只能在synchronize修飾的代碼塊或者方法里調(diào)用:
wait( ) 告知被調(diào)用的線程放棄管程進(jìn)入睡眠直到其他線程進(jìn)入相同管程并且調(diào)用notify( )挣饥。
notify( ) 恢復(fù)相同對(duì)象中第一個(gè)調(diào)用 wait( ) 的線程除师。
notifyAll( ) 恢復(fù)相同對(duì)象中所有調(diào)用 wait( ) 的線程。具有最高優(yōu)先級(jí)的線程最先運(yùn)行扔枫。
package com.DeeJay.ThreadDemo;
public class Q {
private int n; // 生產(chǎn)的對(duì)象
private boolean isDataReady = false;
public int get() throws InterruptedException {
if(!isDataReady){
wait(); // 暫停, 等待著put()來設(shè)置好這個(gè)值只后再讀取
}
System.out.println("Get " + n);
isDataReady = false; // 標(biāo)記已經(jīng)取走了值
notify(); // 通知put()可以設(shè)置另外一個(gè)值了
return this.n;
}
public void put(int n) throws InterruptedException {
// 如果這個(gè)值已經(jīng)設(shè)置好了
if (isDataReady) {
wait(); // 等待get()把值取走
}
this.n = n; //設(shè)置一個(gè)新的值
isDataReady = true; // 標(biāo)記已經(jīng)設(shè)置好了
System.out.println("Put: " + n);
notify(); // 通知線程get() 這個(gè)最新的值
}
}
關(guān)于死鎖
死鎖發(fā)生在當(dāng)兩個(gè)線程對(duì)一對(duì)同步對(duì)象有循環(huán)依賴關(guān)系時(shí)
比如說有ABC三個(gè)子線程汛聚,A依賴于B,B依賴于C短荐,C依賴于A倚舀,就有可能出現(xiàn)死鎖。