概述
操作系統(tǒng)幾乎都支持多任務(wù)操作蜒谤,例如:可以聽歌山宾,可以看電影,可以聊天鳍徽。 聽歌塌碌,看電影,聊天就是一個個的任務(wù)旬盯,每個運行中的任務(wù)都是一個進程。就聽歌而言翎猛,可以唱胖翰,可以顯示歌詞,這便是一個個的線程切厘,一個進程中包含了多個順序執(zhí)行流萨咳,每個順序執(zhí)行流就是一個線程。
進程的定義
進程執(zhí)行的程序(程序是靜態(tài)的疫稿,進程是動態(tài)的)培他,或者是一個任務(wù)。 一個進程可以包含一個或多個線程遗座。
線程的定義
線程就是程序中單獨順序的流控制舀凛。線程本身不能運行,它只能用于程序中途蒋。而多線程指的是單個程序中可以同時運行多個不同的線程執(zhí)行不同的小任務(wù)猛遍。 線程是不擁有系統(tǒng)資源的,只能使用分配給程序的資源和環(huán)境。
進程可以看成是一個工廠懊烤,而線程可以看成是工廠中的工人梯醒,為了完成工廠的任務(wù),每個工人都要去完成自己所負責(zé)的小任務(wù)腌紧, 從而使進程完成它的任務(wù)茸习。 多進程允許多個任務(wù)同時運行,多線程是一個任務(wù)分成不同部分運行壁肋。
進程和線程的區(qū)別
- 多個進程的內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨立的号胚,不會互相影響。 而多線程是共享一塊內(nèi)存空間和一組系統(tǒng)資源墩划,有可能互相影響涕刚。
- 線程本身的數(shù)據(jù)通常只有寄存器數(shù)據(jù),以及一個程序執(zhí)行使用過的堆棧乙帮,所以線程的切換比進程切換的負擔要小杜漠。
- 一個進程由多個線程組成,線程是進程的進行單元察净。當進程被初始化后驾茴,主線程就被創(chuàng)建了。一個線程必須有一個父進程氢卡。
一個程序運行后至少有一個進程锈至,一個進程里可以包含多個線程,但至少要包含一個線程译秦。
線程的創(chuàng)建和啟動
JAVA中使用Threadk類來代表線程峡捡,所有的線程對象都是Thread類或者子類的實例。每個線程的作用是完成一定的任務(wù)筑悴,實際上就是進行一段程序流们拙,JAVA使用線程執(zhí)行體來代表這段程序流。一個線程不能被重現(xiàn)啟動在它執(zhí)行完成之后阁吝。
線程創(chuàng)建的方式:
- 繼承Thread類砚婆,然后重寫run方法
- 實現(xiàn)Runable接口 ,實現(xiàn)其run方法
- 使用Callable和Future創(chuàng)建線程
將希望線程執(zhí)行的代碼放到run方法中突勇,然后使用start方法來啟動線程装盯,start方法首先為線程的執(zhí)行準備好系統(tǒng)資源,在去調(diào)用run方法甲馋。
下面看一下各個線程的實現(xiàn)代碼
通過繼承Thread類實現(xiàn)
public class ThreadTest {
public static void main(String[] args) {
Thread1 t = new Thread1() ;
t.start();
}
}
class Thread1 extends Thread{
@Override
public void run(){
for (int i = 0 ; i < 100 ; i++) {
System.out.println("hello thread " + i );
}
}
}
通過實現(xiàn)Runable接口實現(xiàn):
public class Thread2Test {
public static void main(String[] args) {
Thread2 t = new Thread2();
Thread tt = new Thread(t) ;
tt.start();
}
}
class Thread2 implements Runnable{
@Override
public void run() {
for (int i = 0 ; i < 100 ; i++){
System.out.println("hello world " + i);
}
}
}
//通過靜態(tài)內(nèi)部類的方式創(chuàng)建多線程
public class StaticTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0 ; i <20 ; i++) {
System.out.println("hello world " + i );
}
}
}) ;
t.start();
for (int i = 0 ; i < 20 ; i++){
System.out.println("main " + i );
}
}
}
通過上面兩種線程啟動方式的比較埂奈,是否會有這樣的疑問,繼承Thread為什么要重寫run方法定躏,通過實現(xiàn)Runable接口的類挥转,為什么要作為Thread類的構(gòu)造參數(shù)海蔽,才能啟動線程,取源碼中尋找一下答案绑谣。
public
class Thread implements Runnable { //Thread類也實現(xiàn)了Runable接口
//無參構(gòu)造方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
//參數(shù)為Runable類型的構(gòu)造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//線程默認的名字是Thread-number 党窜,通過下面的代碼實現(xiàn), threadInitNumber靜態(tài)成員變量,被所有的線程對象共享
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public Thread(String name) {
init(null, null, name, 0); //指定線程的名字
}
//... 還有其他的構(gòu)造方法借宵,不再羅列
//私有初始化方法
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
init(g, target, name, stackSize, null);
}
public synchronized void start() {
if (threadStatus != 0)
group.add(this);
boolean started = false;
try {
start0(); //start0 為一個native方法幌衣,調(diào)用c來分配系統(tǒng)資源
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//run方法,如果target為空壤玫,則什么都不做豁护,否則執(zhí)行target的run方法。
public void run() {
if (target != null) {
target.run();
}
}
//調(diào)用getName方法可以返回線程的名字
public final String getName() {
return new String(name, true);
}
//由于主要介紹多線程欲间,Thread的其他方法不再過多的羅列楚里,可以自行查閱源碼文件
}
當使用第一種方式來生成線程對象的時候,必須要重寫run方法猎贴,因為Thread類中的run方法班缎,當target為空時,并不做任何的操作她渴。第二方式來生成線程對象時达址,需要實現(xiàn)run方法,然后使用new Thread(new myThread())來生成線程對象趁耗。target不為空沉唠,就會調(diào)用target類的run 方法 。
使用Callable和Future創(chuàng)建線程
public class Thread3Test {
public static void main(String[] args) {
ThirdThread t3 = new ThirdThread() ;
FutureTask<Integer> futureTask = new FutureTask<Integer>(t3);
ThirdThread1 t4 = new ThirdThread1() ;
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(t4);
Thread t = new Thread(futureTask);
Thread t1 = new Thread(futureTask1);
t.start();
t1.start();
try {
System.out.println(futureTask.get());
System.out.println(futureTask1.get());
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThirdThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0 ;
for ( ; i <5 ; i++){
System.out.println("hello " + i );
}
return i ;
}
}
class ThirdThread1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0 ;
for ( ; i <5 ; i++){
System.out.println("world " + i );
}
return i ;
}
}
//運行結(jié)果 苛败,多線程的運行結(jié)果是不可預(yù)測的满葛,可能回和我的運行結(jié)果不同
hello 0
world 0
hello 1
world 1
hello 2
world 2
world 3
world 4
hello 3
hello 4
5
5
main
為了說明效果,特寫了兩個類罢屈,啟動了兩個線程嘀韧,從結(jié)果看,多線程的效果確實是產(chǎn)生了儡遮。但是通過上面的代碼發(fā)現(xiàn),使用了FutureTask對實現(xiàn)了Callable接口的類暗赶,進行了包裝鄙币,然后將FutureTask實例傳入了Thread類的構(gòu)造方法中,這又是為啥蹂随? Thread的構(gòu)造方法只能接受Runable的類型十嘿,Call接口并沒有繼承Runable接口,無法作為參數(shù)傳入Thread構(gòu)造方法中岳锁,所以使用了FutureTask來進行包裝绩衷,因為FutureTask實現(xiàn)了Runable接口,并實現(xiàn)了run方法,在該方法中調(diào)用了Callable接口的call方法咳燕,并將call方法的返回值賦值給了result變量勿决。調(diào)用get()方法便能獲取返回值。
三種創(chuàng)建方式總結(jié):
通過繼承Thread類招盲,實現(xiàn)Runable接口和Callable接口都可以創(chuàng)建多線程低缩,繼承Thread類,并重寫run方法 曹货,可以直接通過調(diào)用start()方法實現(xiàn)多線程咆繁,實現(xiàn)Runable接口,則需要借助Thread類實現(xiàn)顶籽,將Runable實例玩般,傳入Thread的構(gòu)造方法中,在調(diào)用start()方法實現(xiàn)多線程礼饱。實現(xiàn)Callable接口坏为,則需要使用FutureTask來包裝,并將FutureTask實例傳入Thread的構(gòu)造方法中實現(xiàn)多線程慨仿。實現(xiàn)Runable和Callable接口的不同之處是久脯,Callable接口中的call方法可以返回值,并可以拋出異常镰吆。
線程的生命周期
線程要經(jīng)過:新建帘撰、就緒、運行万皿、阻塞摧找、死亡五種狀態(tài)。
生命周期的轉(zhuǎn)化如下:
線程同步
在多線程的環(huán)境中牢硅,可能會有兩個甚至多個的線程試圖同時訪問一個有限的資源蹬耘。對于這種潛在的資源沖突進行預(yù)防。解決的方法便是在資源上為其加鎖减余,對象加鎖之后综苔,其他線程便不能再訪問加鎖的資源。
線程安全的問題位岔,比較突出的便是銀行賬戶的問題如筛,好多書中都是拿這個在說明多線程對有限資源的競爭問題。
為了解決對這種有限資源的競爭抒抬,java提供了synchronized關(guān)鍵字來解決這個問題杨刨。 synchronized可以修飾方法,被synchronized修飾的方法稱為同步方法擦剑。
java中的每個對象都有一個鎖(lock)或者叫做監(jiān)視器(monitor),當訪問某個對象的synchronized方法時妖胀,表示將該對象上鎖芥颈,此時其他的任何線程都無法在去訪問該synchronized方法了,直到之前的那個線程執(zhí)行完畢后(或者拋出異常)赚抡,將該對象的鎖釋放掉爬坑,其他線程才有可能再去訪問synchronized方法。
對于同步方法而言怕品,無須顯式的指定同步監(jiān)視器妇垢,同步方法的同步監(jiān)視器是this,也就是對象本身肉康。線程在開始進行同步方法或者同步代碼塊之前闯估,必須先獲得對同步監(jiān)視器的鎖定。所以任意時刻只能有一個線程獲得對對象的訪問操作吼和。
synchronized方法是一種粗粒度的并發(fā)控制涨薪,synchronized塊是一種細粒度的并發(fā)控制,范圍更小一點炫乓。
Java5之后刚夺,java提供了一種功能更加強大的線程同步機制,顯示的定義同步鎖對象來實現(xiàn)同步末捣,在這種機制下侠姑,同步鎖使用Lock對象充當。
代碼如下:
class LockTest{
private final ReentrantLock lock = new ReentrantLock();
public void method(){
lock.lock();
try{
....
}finally{
lock.unlock() ;
}
}
}
使用Lock與使用同步方法有點相似箩做,使用Lock時時顯式使用Lock對象作為同步鎖莽红,同步方法是隱式使用當前對象作為同步監(jiān)視器。
死鎖
當兩個線程相互等待對象釋放同步監(jiān)視器時就會發(fā)生死鎖邦邦,一旦發(fā)生死鎖安吁,程序不會出現(xiàn)任何異常和任何提示,所以應(yīng)該避免線程之間相互等待對方釋放資源的情況出現(xiàn)燃辖。
傳統(tǒng)的線程通信
傳統(tǒng)的線程通信鬼店,借助了Object類中的wait() ,notify(),notifyAll()三個方法,這三個方法并不屬于Thread類黔龟。
如果使用sychronized來保證同步妇智,通信則依靠下面的方法:
wait():導(dǎo)致當前線程阻塞,知道其他線程調(diào)用該同步監(jiān)視器的notify()或者notifyAll()方法氏身,該線程才會被喚醒巍棱,喚醒后并不會立即執(zhí)行,而是等待處理器的調(diào)度观谦。
notify():喚醒此同步監(jiān)視器中等待的單個線程拉盾。
notifyAll():喚醒同步監(jiān)視器中等待的所有線程桨菜。
wait()和notify() 或 wait()和notifyAll()是成對出現(xiàn)的豁状,因為他們依靠的都是同一個同步監(jiān)視器捉偏。
使用Condition控制線程通信
如果程序沒有使用synchronized來保證同步,而是使用了Lock對象來保證同步泻红,則系統(tǒng)中就不存在隱式的同步監(jiān)視器夭禽,也就不能使用wait(),notify(),notifyAll()方法進行通信了。 Java提供了Condition來進行線程之間的通信谊路。
Condition提供了3個方法來進行線程通信:
await(): 導(dǎo)致當前線程等待讹躯,知道其他線程調(diào)用signal()或者signalAll()方法
signal(): 喚醒此Lock對象上的單個線程。
signalAll():喚醒此Lock對象上的所有線程缠劝。
線程池
創(chuàng)建一個新的線程成本還是比較高的潮梯,因為它涉及了程序與操作系統(tǒng)之間的交互,所以使用線程池可以很好的提高性能惨恭,尤其是程序的線程的生命周期很短秉馏,需要頻繁的創(chuàng)建線程的時候。
Java5提供了內(nèi)建的線程池支持脱羡,新增了一個Executors工廠類來產(chǎn)生線程萝究。
- newCachedThreadPool():創(chuàng)建一個具有緩存功能的線程池,系統(tǒng)根據(jù)需要創(chuàng)建線程锉罐,這些線程將會被緩存在線程池中 帆竹。
- newFixedThreadPool(int nThreads): 創(chuàng)建一個可重用,固定線程數(shù)的線程池脓规。
- newSingleThreadPool():創(chuàng)建一個只有單線程的線程池
上面三個方法返回ExecutorService對象代表一個線程池栽连,可以執(zhí)行Runnable對象或者Callable對象所代表的線程
下面這兩個方法返回一個ScheduledExecutorService線程池,是ExecutorService的子類抖拦,可以在指定的延遲后執(zhí)行任務(wù)升酣。
- newScheduledThreadPool(int corePoolSize): 創(chuàng)建具有指定線程數(shù)的線程池,它可以在指定延遲后執(zhí)行線程任務(wù)态罪,corePoolSize指定池中保存的線程數(shù)噩茄,即使線程是空閑的也被保存在線程池中。
- newSingleThreadScheduledExecutor():創(chuàng)建只有一個線程的線程池复颈,它可以在指定延遲后執(zhí)行線程任務(wù)绩聘。
線程池的簡單使用:
public class MyThread extends Thread{
public void run(){
for(itn i = 0 ; i <10 ; i++){
System.out.println("hello world" + i) ;
}
}
}
public class Test{
public static void main(String[] args){
MyThread t = new MyThread();
ExecutorService pool = Exectors.newFixedThreadPool(6);
pool.submit(t) ;
pool.shutdown();
}
}
少年聽雨歌樓上,紅燭昏羅帳耗啦。
壯年聽雨客舟中凿菩,江闊云低,斷雁叫西風(fēng)帜讲。
感謝支持衅谷!
---起個名忒難