概述
操作系統(tǒng)幾乎都支持多任務(wù)操作,例如:可以聽歌,可以看電影,可以聊天。 聽歌,看電影,聊天就是一個(gè)個(gè)的任務(wù),每個(gè)運(yùn)行中的任務(wù)都是一個(gè)進(jìn)程。就聽歌而言,可以唱,可以顯示歌詞,這便是一個(gè)個(gè)的線程,一個(gè)進(jìn)程中包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程。
進(jìn)程的定義
進(jìn)程執(zhí)行的程序(程序是靜態(tài)的,進(jìn)程是動(dòng)態(tài)的),或者是一個(gè)任務(wù)。 一個(gè)進(jìn)程可以包含一個(gè)或多個(gè)線程。
線程的定義
線程就是程序中單獨(dú)順序的流控制。線程本身不能運(yùn)行,它只能用于程序中。而多線程指的是單個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程執(zhí)行不同的小任務(wù)。 線程是不擁有系統(tǒng)資源的,只能使用分配給程序的資源和環(huán)境。
進(jìn)程可以看成是一個(gè)工廠,而線程可以看成是工廠中的工人,為了完成工廠的任務(wù),每個(gè)工人都要去完成自己所負(fù)責(zé)的小任務(wù), 從而使進(jìn)程完成它的任務(wù)。 多進(jìn)程允許多個(gè)任務(wù)同時(shí)運(yùn)行,多線程是一個(gè)任務(wù)分成不同部分運(yùn)行。
進(jìn)程和線程的區(qū)別
- 多個(gè)進(jìn)程的內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨(dú)立的,不會(huì)互相影響。 而多線程是共享一塊內(nèi)存空間和一組系統(tǒng)資源,有可能互相影響。
- 線程本身的數(shù)據(jù)通常只有寄存器數(shù)據(jù),以及一個(gè)程序執(zhí)行使用過的堆棧,所以線程的切換比進(jìn)程切換的負(fù)擔(dān)要小。
- 一個(gè)進(jìn)程由多個(gè)線程組成,線程是進(jìn)程的進(jìn)行單元。當(dāng)進(jìn)程被初始化后,主線程就被創(chuàng)建了。一個(gè)線程必須有一個(gè)父進(jìn)程。
一個(gè)程序運(yùn)行后至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程里可以包含多個(gè)線程,但至少要包含一個(gè)線程。
線程的創(chuàng)建和啟動(dòng)
JAVA中使用Threadk類來代表線程,所有的線程對(duì)象都是Thread類或者子類的實(shí)例。每個(gè)線程的作用是完成一定的任務(wù),實(shí)際上就是進(jìn)行一段程序流,JAVA使用線程執(zhí)行體來代表這段程序流。一個(gè)線程不能被重現(xiàn)啟動(dòng)在它執(zhí)行完成之后。
線程創(chuàng)建的方式:
- 繼承Thread類,然后重寫run方法
- 實(shí)現(xiàn)Runable接口 ,實(shí)現(xiàn)其run方法
- 使用Callable和Future創(chuàng)建線程
將希望線程執(zhí)行的代碼放到run方法中,然后使用start方法來啟動(dòng)線程,start方法首先為線程的執(zhí)行準(zhǔn)備好系統(tǒng)資源,在去調(diào)用run方法。
下面看一下各個(gè)線程的實(shí)現(xiàn)代碼
通過繼承Thread類實(shí)現(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 );
}
}
}
通過實(shí)現(xiàn)Runable接口實(shí)現(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 );
}
}
}
通過上面兩種線程啟動(dòng)方式的比較,是否會(huì)有這樣的疑問,繼承Thread為什么要重寫run方法,通過實(shí)現(xiàn)Runable接口的類,為什么要作為Thread類的構(gòu)造參數(shù),才能啟動(dòng)線程,取源碼中尋找一下答案。
public
class Thread implements Runnable { //Thread類也實(shí)現(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);
}
//線程默認(rèn)的名字是Thread-number ,通過下面的代碼實(shí)現(xiàn), threadInitNumber靜態(tài)成員變量,被所有的線程對(duì)象共享
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 為一個(gè)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的其他方法不再過多的羅列,可以自行查閱源碼文件
}
當(dāng)使用第一種方式來生成線程對(duì)象的時(shí)候,必須要重寫run方法,因?yàn)門hread類中的run方法,當(dāng)target為空時(shí),并不做任何的操作。第二方式來生成線程對(duì)象時(shí),需要實(shí)現(xiàn)run方法,然后使用new Thread(new myThread())來生成線程對(duì)象。target不為空,就會(huì)調(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 ;
}
}
//運(yùn)行結(jié)果 ,多線程的運(yùn)行結(jié)果是不可預(yù)測(cè)的,可能回和我的運(yùn)行結(jié)果不同
hello 0
world 0
hello 1
world 1
hello 2
world 2
world 3
world 4
hello 3
hello 4
5
5
main
為了說明效果,特寫了兩個(gè)類,啟動(dòng)了兩個(gè)線程,從結(jié)果看,多線程的效果確實(shí)是產(chǎn)生了。但是通過上面的代碼發(fā)現(xiàn),使用了FutureTask對(duì)實(shí)現(xiàn)了Callable接口的類,進(jìn)行了包裝,然后將FutureTask實(shí)例傳入了Thread類的構(gòu)造方法中,這又是為啥? Thread的構(gòu)造方法只能接受Runable的類型,Call接口并沒有繼承Runable接口,無法作為參數(shù)傳入Thread構(gòu)造方法中,所以使用了FutureTask來進(jìn)行包裝,因?yàn)镕utureTask實(shí)現(xiàn)了Runable接口,并實(shí)現(xiàn)了run方法,在該方法中調(diào)用了Callable接口的call方法,并將call方法的返回值賦值給了result變量。調(diào)用get()方法便能獲取返回值。
三種創(chuàng)建方式總結(jié):
通過繼承Thread類,實(shí)現(xiàn)Runable接口和Callable接口都可以創(chuàng)建多線程,繼承Thread類,并重寫run方法 ,可以直接通過調(diào)用start()方法實(shí)現(xiàn)多線程,實(shí)現(xiàn)Runable接口,則需要借助Thread類實(shí)現(xiàn),將Runable實(shí)例,傳入Thread的構(gòu)造方法中,在調(diào)用start()方法實(shí)現(xiàn)多線程。實(shí)現(xiàn)Callable接口,則需要使用FutureTask來包裝,并將FutureTask實(shí)例傳入Thread的構(gòu)造方法中實(shí)現(xiàn)多線程。實(shí)現(xiàn)Runable和Callable接口的不同之處是,Callable接口中的call方法可以返回值,并可以拋出異常。
線程的生命周期
線程要經(jīng)過:新建、就緒、運(yùn)行、阻塞、死亡五種狀態(tài)。
生命周期的轉(zhuǎn)化如下:

線程同步
在多線程的環(huán)境中,可能會(huì)有兩個(gè)甚至多個(gè)的線程試圖同時(shí)訪問一個(gè)有限的資源。對(duì)于這種潛在的資源沖突進(jìn)行預(yù)防。解決的方法便是在資源上為其加鎖,對(duì)象加鎖之后,其他線程便不能再訪問加鎖的資源。
線程安全的問題,比較突出的便是銀行賬戶的問題,好多書中都是拿這個(gè)在說明多線程對(duì)有限資源的競(jìng)爭(zhēng)問題。
為了解決對(duì)這種有限資源的競(jìng)爭(zhēng),java提供了synchronized關(guān)鍵字來解決這個(gè)問題。 synchronized可以修飾方法,被synchronized修飾的方法稱為同步方法。
java中的每個(gè)對(duì)象都有一個(gè)鎖(lock)或者叫做監(jiān)視器(monitor),當(dāng)訪問某個(gè)對(duì)象的synchronized方法時(shí),表示將該對(duì)象上鎖,此時(shí)其他的任何線程都無法在去訪問該synchronized方法了,直到之前的那個(gè)線程執(zhí)行完畢后(或者拋出異常),將該對(duì)象的鎖釋放掉,其他線程才有可能再去訪問synchronized方法。
對(duì)于同步方法而言,無須顯式的指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,也就是對(duì)象本身。線程在開始進(jìn)行同步方法或者同步代碼塊之前,必須先獲得對(duì)同步監(jiān)視器的鎖定。所以任意時(shí)刻只能有一個(gè)線程獲得對(duì)對(duì)象的訪問操作。
synchronized方法是一種粗粒度的并發(fā)控制,synchronized塊是一種細(xì)粒度的并發(fā)控制,范圍更小一點(diǎn)。
Java5之后,java提供了一種功能更加強(qiáng)大的線程同步機(jī)制,顯示的定義同步鎖對(duì)象來實(shí)現(xiàn)同步,在這種機(jī)制下,同步鎖使用Lock對(duì)象充當(dāng)。
代碼如下:
class LockTest{
private final ReentrantLock lock = new ReentrantLock();
public void method(){
lock.lock();
try{
....
}finally{
lock.unlock() ;
}
}
}
使用Lock與使用同步方法有點(diǎn)相似,使用Lock時(shí)時(shí)顯式使用Lock對(duì)象作為同步鎖,同步方法是隱式使用當(dāng)前對(duì)象作為同步監(jiān)視器。
死鎖
當(dāng)兩個(gè)線程相互等待對(duì)象釋放同步監(jiān)視器時(shí)就會(huì)發(fā)生死鎖,一旦發(fā)生死鎖,程序不會(huì)出現(xiàn)任何異常和任何提示,所以應(yīng)該避免線程之間相互等待對(duì)方釋放資源的情況出現(xiàn)。
傳統(tǒng)的線程通信
傳統(tǒng)的線程通信,借助了Object類中的wait() ,notify(),notifyAll()三個(gè)方法,這三個(gè)方法并不屬于Thread類。
如果使用sychronized來保證同步,通信則依靠下面的方法:
wait():導(dǎo)致當(dāng)前線程阻塞,知道其他線程調(diào)用該同步監(jiān)視器的notify()或者notifyAll()方法,該線程才會(huì)被喚醒,喚醒后并不會(huì)立即執(zhí)行,而是等待處理器的調(diào)度。
notify():?jiǎn)拘汛送奖O(jiān)視器中等待的單個(gè)線程。
notifyAll():喚醒同步監(jiān)視器中等待的所有線程。
wait()和notify() 或 wait()和notifyAll()是成對(duì)出現(xiàn)的,因?yàn)樗麄円揽康亩际峭粋€(gè)同步監(jiān)視器。
使用Condition控制線程通信
如果程序沒有使用synchronized來保證同步,而是使用了Lock對(duì)象來保證同步,則系統(tǒng)中就不存在隱式的同步監(jiān)視器,也就不能使用wait(),notify(),notifyAll()方法進(jìn)行通信了。 Java提供了Condition來進(jìn)行線程之間的通信。
Condition提供了3個(gè)方法來進(jìn)行線程通信:
await(): 導(dǎo)致當(dāng)前線程等待,知道其他線程調(diào)用signal()或者signalAll()方法
signal(): 喚醒此Lock對(duì)象上的單個(gè)線程。
signalAll():喚醒此Lock對(duì)象上的所有線程。
線程池
創(chuàng)建一個(gè)新的線程成本還是比較高的,因?yàn)樗婕傲顺绦蚺c操作系統(tǒng)之間的交互,所以使用線程池可以很好的提高性能,尤其是程序的線程的生命周期很短,需要頻繁的創(chuàng)建線程的時(shí)候。
Java5提供了內(nèi)建的線程池支持,新增了一個(gè)Executors工廠類來產(chǎn)生線程。
- newCachedThreadPool():創(chuàng)建一個(gè)具有緩存功能的線程池,系統(tǒng)根據(jù)需要?jiǎng)?chuàng)建線程,這些線程將會(huì)被緩存在線程池中 。
- newFixedThreadPool(int nThreads): 創(chuàng)建一個(gè)可重用,固定線程數(shù)的線程池。
- newSingleThreadPool():創(chuàng)建一個(gè)只有單線程的線程池
上面三個(gè)方法返回ExecutorService對(duì)象代表一個(gè)線程池,可以執(zhí)行Runnable對(duì)象或者Callable對(duì)象所代表的線程
下面這兩個(gè)方法返回一個(gè)ScheduledExecutorService線程池,是ExecutorService的子類,可以在指定的延遲后執(zhí)行任務(wù)。
- newScheduledThreadPool(int corePoolSize): 創(chuàng)建具有指定線程數(shù)的線程池,它可以在指定延遲后執(zhí)行線程任務(wù),corePoolSize指定池中保存的線程數(shù),即使線程是空閑的也被保存在線程池中。
- newSingleThreadScheduledExecutor():創(chuàng)建只有一個(gè)線程的線程池,它可以在指定延遲后執(zhí)行線程任務(wù)。
線程池的簡(jiǎn)單使用:
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)。
感謝支持!
---起個(gè)名忒難