進程:正在進行中的程序。其實進程就是一個應用程序運行時的內存分配空間。
線程:其實就是進程中一個程序執行控制單元,一條執行路徑。進程負責的是應用程序的空間的標示。線程負責的是應用程序的執行順序。
一個進程至少有一個線程在運行,當一個進程中出現多個線程時,就稱這個應用程序是多線程應用程序,每個線程在棧區中都有自己的執行空間,自己的方法區、自己的變量。
jvm在啓動的時,首先有一個主線程,負責程序的執行,調用的是main函數。主線程執行的代碼都在main方法中。 當產生垃圾時,收垃圾的動作,是不需要主線程來完成,因爲這樣,會出現主線程中的代碼執行會停止,會去運行垃圾回收器代碼,效率較低,所以由單獨一個線程來負責垃圾回收。
隨機性的原理:因爲cpu的快速切換造成,哪個線程獲取到了cpu的執行權,哪個線程就執行。
返回當前線程的名稱:Thread.currentThread().getName()
線程的名稱是由:Thread-編號定義的。編號從0開始。 線程要運行的代碼都統一存放在了run方法中。 線程要運行必須要通過類中指定的方法開啓。start方法。(啓動後,就多了一條執行路徑)
start方法:1)、啓動了線程;2)、讓jvm調用了run方法。
創建線程的第一種方式:繼承Thread ,由子類複寫run方法。
步驟:
1,定義類繼承Thread類;
2,目的是複寫run方法,將要讓線程運行的代碼都存儲到run方法中;
3,通過創建Thread類的子類對象,創建線程對象;
4,調用線程的start方法,開啓線程,並執行run方法。
線程狀態:
被創建:start()
運行: 具備執行資格,同時具備執行權;
凍結: sleep(time),wait()—notify()喚醒;線程釋放了執行權,同時釋放執行資格;
臨時阻塞狀態:線程具備cpu的執行資格,沒有cpu的執行權;
消亡:stop()
創建線程的第二種方式:實現一個接口Runnable。
步驟:
1,定義類實現Runnable接口。
2,覆蓋接口中的run方法(用於封裝線程要運行的代碼)。
3,通過Thread類創建線程對象;
4,將實現了Runnable接口的子類對象作爲實際參數傳遞給Thread類中的構造函數。
爲什麼要傳遞呢?因爲要讓線程對象明確要運行的run方法所屬的對象。
5,調用Thread對象的start方法。開啓線程,並運行Runnable接口子類中的run方法。
Ticket t = new Ticket();
/*
直接創建Ticket對象,並不是創建線程對象。
因爲創建對象只能通過new Thread類,或者new Thread類的子類纔可以。
所以最終想要創建線程。既然沒有了Thread類的子類,就只能用
Thread類。
*/
Thread t1 = new Thread(t); //創建線程。
/*
只要將t作爲Thread類的構造函數的實際參數傳入即可完成線程對象和t之間的關聯
爲什麼要將t傳給Thread類的構造函數呢?其實就是爲了明確線程要運行的代碼run方法。
*/
t1.start();
爲什麼要有Runnable接口的出現?
1:通過繼承Thread類的方式,可以完成多線程的建立。但是這種方式有一個侷限性,如果一個類已經有了自己的父類,就不可以繼承Thread類,因爲java單繼承的侷限性。
可是該類中的還有部分代碼需要被多個線程同時執行。這時怎麼辦呢?
只有對該類進行額外的功能擴展,java就提供了一個接口Runnable。這個接口中定義了run方法,其實run方法的定義就是爲了存儲多線程要運行的代碼。
所以,通常創建線程都用第二種方式。 因爲實現Runnable接口可以避免單繼承的侷限性。
2:其實是將不同類中需要被多線程執行的代碼進行抽取。將多線程要運行的代碼的位置單獨定義到接口中。爲其他類進行功能擴展提供了前提。 所以Thread類在描述線程時,內部定義的run方法,也來自於Runnable接口。
實現Runnable接口可以避免單繼承的侷限性。而且,繼承Thread,是可以對Thread類中的方法,進行子類複寫的。但是不需要做這個複寫動作的話,只爲定義線程代碼存放位置,實現Runnable接口更方便一些。所以Runnable接口將線程要執行的任務封裝成了對象。
-------------------------------------------------------
//面試
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnable run");
}
})
{
public void run(){
System.out.println("subthread run");
}
}.start(); //結果:subthread run
------------------------------------------------------------
- 同步代碼塊使用的鎖可以是任意對象。
- 同步函數使用的鎖是this,靜態同步函數的鎖是該類的字節碼文件對象。
- 在一個類中只有一個同步的話,可以使用同步函數。如果有多同步,必須使用同步代碼塊,來確定不同的鎖。所以同步代碼塊相對靈活一些。
★考點問題:請寫一個延遲加載的單例模式?寫懶漢式;當出現多線程訪問時怎麼解決?加同步,解決安全問題;效率高嗎?不高;怎樣解決?通過雙重判斷的形式解決。
//懶漢式:延遲加載方式。
當多線程訪問懶漢式時,因爲懶漢式的方法內對共性數據進行多條語句的操作。所以容易出現線程安全問題。爲了解決,加入同步機制,解決安全問題。但是卻帶來了效率降低。
爲了效率問題,通過雙重判斷的形式解決。
class Single{
private static Single s = null; private Single(){}
public static Single getInstance(){ //鎖是誰?字節碼文件對象;
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
---------------------------------------------------------
同步死鎖:通常只要將同步進行嵌套,就可以看到現象。同步函數中有同步代碼塊,同步代碼塊中還有同步函數。
線程間通信:
思路:多個線程在操作同一個資源,但是操作的動作卻不一樣。
1:將資源封裝成對象。
2:將線程執行的任務(任務其實就是run方法。)也封裝成對象。
等待喚醒機制:涉及的方法:
wait:將同步中的線程處於凍結狀態。釋放了執行權,釋放了資格。同時將線程對象存儲到線程池中。
notify:喚醒線程池中某一個等待線程。 notifyAll:喚醒的是線程池中的所有線程。
注意:
1:這些方法都需要定義在同步中。
2:因爲這些方法必須要標示所屬的鎖。
你要知道 A鎖上的線程被wait了,那這個線程就相當於處於A鎖的線程池中,只能A鎖的notify喚醒。
3:這三個方法都定義在Object類中。爲什麼操作線程的方法定義在Object類中?
因爲這三個方法都需要定義同步內,並標示所屬的同步鎖,既然被鎖調用,而鎖又可以是任意對象,那麼能被任意對象調用的方法一定定義在Object類中。
wait和sleep區別: 分析這兩個方法:從執行權和鎖上來分析:
wait:可以指定時間也可以不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。
sleep:必須指定時間,時間到自動從凍結狀態轉成運行狀態(臨時阻塞狀態)。
wait:線程會釋放執行權,而且線程會釋放鎖。
Sleep:線程會釋放執行權,但不是不釋放鎖。
線程的停止:通過stop方法就可以停止線程。但是這個方式過時了。
停止線程:原理就是:讓線程運行的代碼結束,也就是結束run方法。
怎結束麼run方法?一般run方法裏肯定定義循環。所以只要結束循環即可。
第一種方式:定義循環的結束標記。
第二種方式:如果線程處於了凍結狀態,是不可能讀到標記的,這時就需要通過Thread類中的interrupt方法,將其凍結狀態強制清除。讓線程恢復具備執行資格的狀態,讓線程可以讀到標記,並結束。
---------< java.lang.Thread >----------
interrupt():中斷線程。
setPriority(int newPriority):更改線程的優先級。
getPriority():返回線程的優先級。
toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。
Thread.yield():暫停當前正在執行的線程對象,並執行其他線程。
setDaemon(true):將該線程標記爲守護線程或用戶線程。將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。該方法必須在啓動線程前調用。
join:臨時加入一個線程的時候可以使用join方法。
當A線程執行到了B線程的join方式。A線程處於凍結狀態,釋放了執行權,B開始執行。A什麼時候執行呢?只有當B線程運行結束後,A才從凍結狀態恢復運行狀態執行。
LOCK的出現替代了同步:lock.lock();„„„lock.unlock();
Lock接口:多線程在JDK1.5版本升級時,推出一個接口Lock接口。
解決線程安全問題使用同步的形式,(同步代碼塊,要麼同步函數)其實最終使用的都是鎖機制。
到了後期版本,直接將鎖封裝成了對象。線程進入同步就是具備了鎖,執行完,離開同步,就是釋放了鎖。
在後期對鎖的分析過程中,發現,獲取鎖,或者釋放鎖的動作應該是鎖這個事物更清楚。所以將這些動作定義在了鎖當中,並把鎖定義成對象。
所以同步是隱示的鎖操作,而Lock對象是顯示的鎖操作,它的出現就替代了同步。
在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因爲同步中的鎖是任意對象,所以操作鎖的等待喚醒的方法都定義在Object類中。
而現在鎖是指定對象Lock。所以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中並沒有直接操作等待喚醒的方法,而是將這些方式又單獨封裝到了一個對象中。這個對象就是Condition,將Object中的三個方法進行單獨的封裝。並提供了功能一致的方法 await()、signal()、signalAll()體現新版本對象的好處。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try { while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0; --count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}