java基礎(三):多執行緒
1.程序、執行緒
程序:正在執行的程式
執行緒:程序中負責程式執行的執行單元
即:程序的範圍>執行緒的範圍。
且:一個程序可以有多個執行緒。
2.多執行緒的意義:多部分程式碼同時執行,提高CPU使用效率
3.多執行緒的特點:CPU的隨機性
4.建立執行緒的兩種方法
(1).繼承Thread
^1.繼承Thread
^2.覆蓋run()方法
^3.建立執行緒物件
^4.呼叫start()方法開啟執行緒
(2).實現Runnable介面
^1.實現Runnable介面
^2.覆蓋run()方法
^3.新建Thread物件,在該物件建構函式時將上述物件作為引數傳入
^4.呼叫start()方法。
比較:第一種因為是繼承,java中的繼承都是單繼承,有侷限性。第二種比較靈活,且耦合也比較低,建議。
5.執行緒的狀態變化關係
執行緒的狀態有五種:被建立,執行,凍結,阻塞,消亡
被建立->執行:start()方法。
執行->消亡:run()方法結束。
執行->凍結:sleep(time),wait()兩種方法。
凍結->執行:sleep時間到了,notify()
執行,阻塞,凍結之間的關係:
執行:有CPU執行資格,有CPU執行權
阻塞:有CPU執行資格,無CPU執行權
凍結:無CPU執行資格,無CPU執行權
6.執行緒安全:當多個執行緒同時操作同一個資料時,會導致資料錯誤,造成執行緒安全問題。
(1)解決方法:(1).同步程式碼塊 (2)同步函式
同步程式碼塊:synchronized(物件){程式碼};(可用try,catch解決錯誤)
同步函式:正常的函式上加synchronized。
常規函式的鎖:this
靜態函式的鎖:類名.class(位元組碼物件)
(2)比較:同步函式一個類只有一個鎖,所以一個類一個鎖的情況用同步函式。一個類多個鎖或者多個類用一個鎖的用同步程式碼塊。
(3)加鎖為什麼能保證執行緒安全?因為加了鎖就用了單例模式。
單例模式分成2種:餓漢式,懶漢式。
(1)餓漢式(先建立物件,然後通過方法返回該物件)
(2)懶漢式(先不建立物件,通過方法返回時再建立物件)
class Single{//餓漢式
private Single single = new Single();
Single(){}
public Single getInstance(){
return single;
}
}
class Single{//懶漢式,雙重if判斷保證安全
private Single single = null;
Single(){}
public Single getInstance(){
if(single == null){
synchronized(Single.class){
if(single == null){
single = new Single();
}
}
}
return single;
}
}
7.死鎖:加鎖時需要注意導致一些新的問題,即死鎖。
死鎖的原因:(1).同步巢狀 (2).生產者消費者的同一方(生產者轉到另一個生產者,消費者轉到另一個消費者)
(1).同步巢狀:A執行緒擁有CPU執行權,但是A內還巢狀一個B。如果A想執行B需要把執行權給B,但是B拿到執行權又進不了A,就在互相等待,造成死鎖。
synchronized(A.class){
synchronized(B.class){
xxx;
}
}
(2).生產者消費者模式的幾個規則:
^1.生產時不可以消費(多執行緒且需要同步)
^2.生產一個後就不能生產,讓消費者把生產出來的東西消費掉。然後轉到生產者繼續生產。
所以,如果一個生產者生產好了東西,然後CPU執行權轉給另外一個生產者,這個時候是不可以生產的,2個生產者互相等待別人去消費,造成死鎖。
生產者消費者模式的其他一些細節:
^1.判斷是生產者還是消費者時,可以用一個flag來判斷,但是不要用if(flag),用while(flag)。因為如果一個執行緒剛剛判斷完if,轉到另一個執行緒,則判斷實際是無效的。
^2.為了避免死鎖,一個生產者結束之後,喚醒其他執行緒的時候一定要有消費者執行緒在裡面。所以不用notify(),用notifyAll();
^3.但是緊接著又有一個問題,一個生產者結束後,只需要消費者,但是notifyAll()把生產者也喚醒了,降低效率,所以為了解決效率低的問題,引入一套鎖和監視器機制。
^4.每次只能生產一個產品,效率有點低,所以可以設定陣列,保證可以生產多個,那麼while判斷時是數量判斷。並且每次生產滿後需要將陣列的指標歸為0重新開始。
class Resource(){
private int count;
private boolean flag = false;//一開始預設沒有產品
private String name = "";
//生產
public synchronized void set(String name){
while(flag){//如果有產品了,等待
try{
wait();
}catch(Exception e){
e.printStack();
}
}
count++;
this.name = name + "..." + count;
system.out.println(Thread.currentThread.getName + "生產者" + this.name);
flag = true;
notifyAll();
//消費
public synchronized void get(){
while(!flag){
try{
wait();
}catch(Exception e){
e.printStack();
}
}
system.out.println(Thread.currentThread.getName + "消費者" + this.name);
flag = false;
notifyAll();
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(false)
r.set("麵包");
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this .r = r;
}
public void run(){
while(true)
r.get();
}
}
public class Test{
public static void main(String[] args){
Resource r = new Resource();
Producer p = new Producer(r);
Consumer c = new Consumer(c);
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
鎖和監視器的寫法:
private Lock lock = new ReenTrantLock();
將上述synchronized變成lock.lock();
在finally里加上lock.unlock();
private Condition con = lock.newCondition();
wait()變成con.await();
notify()變成con.signal();
notifyAll()變成con.signalAll();
在喚醒時喚醒需要的那一方即可。
陣列使可以同時生產多個。
生產者:while(count == objs.length), objs[put] = name, if(++put == objs.length) put = 0;
消費者:while(count == 0), name = objs[take], if(++take == objs.length) take = 0;
8.插入一個執行緒先執行
t.join():讓t執行緒先執行,執行完之後再執行原本的執行緒
9.轉化成守護執行緒
t.setDaemon(true):垃圾回收機制是一個很典型的守護執行緒。前臺執行緒結束,守護執行緒也結束。
10.中止執行緒
如果不是在一個執行緒,用while判斷
如果是一個執行緒,用t.interrept();
11.優先順序
t.setPrority(數字):數字1-5,數字越大,CPU傾向分配的資源就越多。
12.wait和sleep異同
同:都會把執行緒從執行狀態變成凍結狀態
異:(1).sleep時間到了會自動從凍結狀態變成執行狀態,wait不會
(2).wait會釋放執行資格和鎖,sleep只會釋放執行資格不會釋放鎖