標(biāo)簽(空格分隔): java
線程和進(jìn)程
進(jìn)程具有獨(dú)立的數(shù)據(jù)空間碾盟,是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨(dú)立單位
獨(dú)立性:進(jìn)程是系統(tǒng)中獨(dú)立存在的實(shí)體瓦糕,它可以擁有自己獨(dú)立的資源颓鲜,每一個進(jìn)程都擁有自己私有的地址空間辣卒,在沒有進(jìn) 過進(jìn)程本身允許的情況下拧晕,一個用戶進(jìn)程不可以訪問其他用戶的地址空間。
動態(tài)性:程序只是一個靜態(tài)的指令集合蜒茄,而進(jìn)程是正在操作系統(tǒng)中活著的指令集合唉擂。在進(jìn)程中加入了時間的概念,進(jìn)程具有自己的生命周期和個不同的狀態(tài)檀葛。這些概念在程序中都是不具備的玩祟。
并發(fā)性:多個進(jìn)程可以在多個處理器上并發(fā)執(zhí)行,多個進(jìn)程之間不會相互影響屿聋。
線程:是輕量級的進(jìn)程空扎,是進(jìn)程的執(zhí)行單元,它擁有自己的堆棧润讥,自己的程序計(jì)數(shù)器和自己的局部變量转锈,但不擁有系統(tǒng)資源,因?yàn)槎鄠€線程共享父進(jìn)程的全部資源楚殿。
一個線程可以創(chuàng)建和撤銷另一個線程撮慨,多個線程可以在一個進(jìn)程中并發(fā)執(zhí)行。
線程的創(chuàng)建和啟動
1脆粥、繼承Thread類創(chuàng)建線程
1.繼承Thread類砌溺,重寫run()
方法。
public class MyThread extends Thread {
@Override
public void run() {
}
}
2.實(shí)例化對象变隔,調(diào)用對象的start()
方法
MyThread thread = new MyThread();
thread.start();
2规伐、使用靜態(tài)代理模式:實(shí)現(xiàn)Runnable接口
1.實(shí)現(xiàn)Runnable
接口
public class MyRun implements Runnable {
@Override
public void run() {
//do Something
}
}
2.new Thread對象,傳入Runnable接口實(shí)例,調(diào)用Thread
類的start方法
Thread thread = new Thread(new MyRun());
thread.start();
兩種方式都可以創(chuàng)建線程匣缘,但是第二種方式猖闪,實(shí)現(xiàn)起來更加靈活,同時也可以實(shí)現(xiàn)多線程數(shù)據(jù)的共享肌厨,推薦使用第二種方式創(chuàng)建線程培慌。
3、實(shí)現(xiàn)Callable接口創(chuàng)建線程
實(shí)現(xiàn)Callable
接口的類和實(shí)現(xiàn)Runnable接口的類都是可以被線程執(zhí)行的任務(wù)夏哭。
幾點(diǎn)不同:
Callable
規(guī)定的方法是call()
方法检柬,而Runnable
規(guī)定的方法是run()
-
call()
方法可以拋出異常献联,run()
不能拋出異常 -
Callable
執(zhí)行任務(wù)后可以拿到返回值竖配,運(yùn)行Callable
任務(wù)后可以拿到一個Future對象,Runnable
的任務(wù)是不能帶有返回值的里逆,Future
表示任務(wù)異步執(zhí)行的結(jié)果进胯,他提供了檢查計(jì)算是否完成的方法,以等待計(jì)算的完成原押,并檢索計(jì)算的結(jié)果胁镐,通過Futrue
對象,可以了解任務(wù)執(zhí)行的情況,可以取消任務(wù)的執(zhí)行盯漂,還可以獲取任務(wù)的執(zhí)行結(jié)果颇玷。
缺點(diǎn)是:比較繁瑣
實(shí)現(xiàn)步驟:
- 創(chuàng)建Callable實(shí)現(xiàn)類重寫call()方法
- 借助執(zhí)行調(diào)度服務(wù)ExecutorService獲取Future對象
ExecutorService ser = Executors.newFixedThreadPool(2);
Future result = ser.submit(實(shí)現(xiàn)類對象);- 獲取值 result.get();
- 停止服務(wù)ser.shutdownNow();
第二種:實(shí)現(xiàn)方式
由于Callable
接口不是Runnable
接口的子類對象,因此不能直接作為Thread
類的Target對象就缆,同時Callable
接口里的call()
方法擁有返回值帖渠,這個又該如何接受呢?
java為我們提供了一個Future
接口竭宰,這個接口是為了用于接受Callable
里的返回結(jié)果空郊,同時為我們提供了一個是實(shí)現(xiàn)類FutureTask
,這個類同時實(shí)現(xiàn)了Future
接口和Runnable
接口切揭,這個類的實(shí)例需要一個Callable
對象的實(shí)例狞甚,因此我們可以將Callable
包裝成FutureTask
的實(shí)例,這樣就同時解決了上面的兩個問題廓旬。
private static void test() {
MyCallable call = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(call);
Thread thread = new Thread(task);
try {
System.out.println("主線程運(yùn)行");
thread.start();
int result = task.get();
System.out.println("\n主線程阻塞完成");
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("程序退出");
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.print(i + "\t");
sum += i;
}
return sum;
}
}
Thread類的常用方法
構(gòu)造方法
/**
這個是最全的構(gòu)造方法
根據(jù)里面的參數(shù)可以組合出多種構(gòu)造
stackSize是線程運(yùn)行時的堆棧大小
*/
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
靜態(tài)方法
Thread.sleep();
Thread.currentThread()
線程狀態(tài)(5種狀態(tài))
一哼审、新生狀態(tài):線程new出來時候的狀態(tài)。擁有自己的堆椩斜空間棺蛛。
二、就緒狀態(tài):新生狀態(tài)---->調(diào)用start()方法--->就緒狀態(tài)巩步。線程在執(zhí)行start()
方法后并不是一定處于運(yùn)行的狀態(tài)旁赊,只是可以接受CPU調(diào)度,叫可運(yùn)行狀態(tài)----就緒狀態(tài)椅野,一旦獲取了CPU的執(zhí)行權(quán)终畅,線程就進(jìn)入運(yùn)行狀態(tài),并自動調(diào)用自己的run()
竟闪。
三离福、運(yùn)行狀態(tài):就緒狀態(tài)的線程,在接受了CPU調(diào)度之后炼蛤,開始運(yùn)行妖爷,在運(yùn)行狀態(tài)的線程執(zhí)行自己run()
方法中的代碼,除非調(diào)用其他方法而終止理朋,或等待某資源而阻塞絮识,有或運(yùn)行完成任務(wù)而死亡。如果在給定的時間片內(nèi)沒有執(zhí)行結(jié)束嗽上,就會被系統(tǒng)換下來回到等待執(zhí)行狀態(tài)(這個過程我們稱為cpu的任務(wù)切換---并發(fā)執(zhí)行)次舌。
四、終止?fàn)顟B(tài)(死亡狀態(tài)):當(dāng)線程體執(zhí)行完畢兽愤,則線程進(jìn)入終止?fàn)顟B(tài)彼念,線程結(jié)束挪圾。終止?fàn)顟B(tài)是線程生命周期中的最后一個階段。線程結(jié)束的原因有兩個逐沙,一個是運(yùn)行的線程完成了它的全部工作哲思,另一個是線程被強(qiáng)制性的終止,如執(zhí)行stop()
方法和destroy()
方法來終止線程(不推薦使用這兩個方法吩案,前者會產(chǎn)生異常也殖,后者是強(qiáng)制終止,不會釋放鎖)务热。
五忆嗜、阻塞狀態(tài):當(dāng)線程在運(yùn)行時,有阻塞的事件發(fā)生崎岂,則線程暫停運(yùn)行捆毫,進(jìn)入阻塞狀態(tài),進(jìn)入阻塞狀態(tài)的線程冲甘,無法沒有CPU執(zhí)行權(quán)绩卤,無法接受CPU的任務(wù)調(diào)度。當(dāng)阻塞解除時江醇,該線程將再次進(jìn)入就緒狀態(tài)濒憋,重新?lián)碛蠧PU執(zhí)行權(quán),可以接受CPU調(diào)度陶夜。處于正在運(yùn)行狀態(tài)的線程凛驮,在某些情況下,執(zhí)行了sleep()
方法条辟,或等待IO設(shè)備等資源黔夭,或讓出CPU的執(zhí)行權(quán)并暫時停止運(yùn)行,進(jìn)入阻塞狀態(tài)羽嫡,在阻塞狀態(tài)的線程就不能就如就緒隊(duì)列本姥,只有當(dāng)引起阻塞的原因解除時,如睡眠時間到杭棵,或等待的IO設(shè)備空閑下來婚惫,線程便進(jìn)入就緒狀態(tài),重新到隊(duì)列中排隊(duì)等待魂爪,被系統(tǒng)選中后從原來停止的位置開始繼續(xù)運(yùn)行先舷。
停止線程的方法
停止一個線程很簡單,就是讓這個線程體執(zhí)行完成就行了甫窟,因此我們使用一個flag來判斷是否結(jié)束以及何時結(jié)束這個循環(huán)密浑,就行了。
一般的情況下:我們自己寫一個方法粗井,調(diào)用這個方法,則設(shè)置標(biāo)記位,結(jié)束運(yùn)行條件浇衬,促使線程體執(zhí)行結(jié)束懒构。
class MyCallable implements Callable<Integer> {
public boolean flag = true;
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 20; i++) {
if (!flag)
break;
Thread.sleep(1000);
System.out.print(i + "\t");
}
return i;
}
/**
*為外界提供停止線程的控制方法
*/
public void stop(){
flag = false;
}
}
多線程并發(fā)安全問題
由于線程在系統(tǒng)的調(diào)度中具有隨機(jī)性,因此當(dāng)多個線程在訪問同一份資源時,很容易出現(xiàn)錯誤.
一些很經(jīng)典的問題:賣票和取錢,
下面我們根據(jù)一個經(jīng)典的賣票程序來分析一下和解決一下線程安全性問題
private int ticket = 20;
@Override
public void run() {
while (ticket>0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "=====" +ticket );
}
}
當(dāng)上面的程序放到多線程中使用時,就會出現(xiàn)并發(fā)安全問題耘擂,可能出現(xiàn)的現(xiàn)象是:同一份資源出現(xiàn)重復(fù)調(diào)用胆剧,
例如,當(dāng)一個線程當(dāng)ticket--醉冤;
代碼完成后現(xiàn)成的cpu資源被搶奪秩霍,這個時候,第二個線程也執(zhí)行了ticket--蚁阳;
這個時候
出現(xiàn)數(shù)字丟失铃绒,有的字重復(fù)出現(xiàn),同時t--螺捐;
在程序中也是分兩步執(zhí)行的颠悬。有可能執(zhí)行了減法操作,還沒來得及賦值定血,就被其它線程搶奪走了CPU執(zhí)行權(quán)赔癌,使得第一個減法操作無效丟失。
解決辦法:使用Synchronized
同步--->同步代碼塊和同步函數(shù)
public class SaleTicket implements Runnable {
private int ticket = 10;
@Override
public void run() {
synchronized (SaleTicket.class) {
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "=====" + ticket);
}
}
}
注:
如果代碼加了同步還是出現(xiàn)了安全問題澜沟,那么就看:
1.多個線程是不是操作的是同一份資源
2.同步的代碼地方是不是使用的是同一個鎖
3.是不是所有的共享數(shù)據(jù)資源都加入了同步
同步方法持有的鎖灾票,是該方法所屬的對象,靜態(tài)同步代碼塊持有的鎖茫虽,是該類的字節(jié)碼文件铝条。
單例設(shè)計(jì)模式---懶漢式
class Single{
private static Single instance = null;
private Single(){}
public static Single getIntance(){
if(instance == null){
synchronized(Single.class){
if(instance == null) {
instance = new Single();
}
}
return instance;
}
}
懶漢式的單例模式的優(yōu)點(diǎn)是延時加載,但是在多線程中會出現(xiàn)問題席噩,使用同步代碼塊可以解決該問題班缰,使用雙重判斷可以稍微提高一下效率。