1. 程式人生 > >黑馬程式設計師--Java基礎之多執行緒

黑馬程式設計師--Java基礎之多執行緒

------- <a href="http://www.itheima.com" target="blank">android培訓</a>、<a href="http://www.itheima.com" target="blank">java培訓</a>、期待與您交流!


Java之多執行緒整理(還好學過作業系統,還不至於太暈)




概述:


程序:是一個正在執行中的程式。
     每一個程序都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
QQ或者迅雷,一啟動就會在記憶體中分配一個空間,有個地址,程序就是用來標誌這個空間的,用於封裝裡邊的控制單元


執行緒:就是程序中的一個獨立的控制單元。
     執行緒在控制著程序的執行。


一個程序中至少有一個執行緒。


Java VM啟動的時候會有一個程序java.exe
該程序中至少有一個執行緒負責java程式的執行,而且這個執行緒執行的程式碼存在於main方法中,該執行緒稱之為主執行緒。


擴充套件:其實更細節說明jvm,jvm啟動不止一個執行緒,還有負責垃圾回收機制的執行緒。


一、Thread
如何在自定義的程式碼中,自定義一個執行緒呢?
通過對api的查詢,java已經提供了對執行緒這類事物的描述就是Thread類。


java.lang.Thread


建立執行緒的第一種方式:繼承Thread類
步驟:
1.定義類繼承Thread
2.複寫Thread類中的run方法
目的:將自定義程式碼存在run方法,讓執行緒執行
3.呼叫執行緒的start方法
  該方法兩個作用:啟動執行緒,呼叫run方法


發現執行結果每一次都不同。因為多個執行緒都獲取cpu執行權,cpu執行到誰,誰就執行。
明確一點:在某一個時刻,只能由一個程式在執行(多核除外)
cpu在做著快速的切換,以達到看上去是同時執行的效果。可以形象把多執行緒的執行行為在互相搶奪cpu的執行權。


主執行緒執行main方法中的程式碼,自己開啟的執行緒執行的是run方法


這就是多執行緒的一個特性:隨機性。誰搶到誰執行,至於執行多長,cpu說了算


為什麼要覆蓋run方法呢?
Thread類用於描述執行緒。該類就定義了一個功能,用於儲存執行緒要執行的程式碼,該儲存功能就是run方法。
也就是說Thread類中的run方法,用於儲存執行緒要執行的程式碼


class Demo extends Thread
{
pubilc void run()
{
for (int x=0;x<60;x++)
System.out.pringln("Demo run"+x);
}
}


class ThreadDemo
{
Demo d = new Demo();//建立好一個執行緒
d.start();//開啟執行緒並執行該執行緒的run方法
d.run();//僅僅是物件呼叫方法,而執行緒建立了並沒有執行。


}




二、建立執行緒兩種方式
建立執行緒兩種方式:繼承方式和實現方式
建立執行緒的兩種方式.png


實現方式和繼承方式的區別:
實現方式好處:避免了單繼承的侷限性。在定義執行緒時,建議使用實現方式
繼承Thread:執行緒程式碼存放Thread子類run方法中。
實現Runnable:執行緒程式碼存在介面的子類run方法


建立執行緒的第一種方式:繼承Thread類
步驟:
1.定義類繼承Thread
2.複寫Thread類中的run方法
目的:將自定義程式碼存在run方法,讓執行緒執行
3.呼叫執行緒的start方法
  該方法兩個作用:啟動執行緒,呼叫run方法


建立執行緒的第二種方式:實現Runnable介面
步驟:
1.定義類實現Runnable介面
2.覆蓋Runnable介面中的run方法
 將執行緒要執行的程式碼存放在該run方法中
3.通過Thread類建立執行緒物件
4.將Runnable介面的子類物件作為實際引數傳遞給Thread類的建構函式
 為什麼要將Runnable介面的子類物件傳遞給Thread的建構函式?因為自定義的run方法所屬的物件是Runnable介面的子類物件,所以要讓執行緒去指定指定物件的run方法,就必須明確該run方法所屬物件。
5.呼叫Thread類的start方法開啟執行緒並呼叫Runnable介面子類的run方法




  三、同步程式碼塊
售票問題出現列印0,-1,-2等錯票,多執行緒的執行出現了安全問題。
原因:當多條語句在操作同一執行緒共享資料時,一個執行緒對多條語句執行了一部分,還沒有執行完,另一個執行緒參與進來執行,導致共享資料的錯誤。
解決辦法:對多條操作共享資料的語句,只能讓一個執行緒都執行完。在執行過程中,其他執行緒不可以參與執行。


java對於多執行緒的安全問題提供的解決方式是同步程式碼塊


synchronized(物件)
{
需要被同步的程式碼
}


物件如同鎖,持有鎖的執行緒可以在同步中執行,沒有持有鎖的執行緒即使獲得cpu的執行權也進不去。經典例子:火車上的衛生間


同步的前提:
1.必須要有兩個或者兩個以上執行緒
2.必須是多個執行緒使用同一個鎖。
必須保證同步中只能有一個執行緒在執行。


好處:解決了多執行緒的安全問題。
弊端:多個執行緒需要判斷鎖,較為消耗資源


四、同步函式的鎖是this
函式需要被物件呼叫,那麼函式都有一個所屬物件引用,就是this,所以同步函式使用的鎖是this


Demo:
/*
同步函式的鎖是this


賣票程式
通過該程式進行驗證
使用兩個執行緒來賣票:一個執行緒在同步程式碼塊中,一個在同步函式中,都執行賣票動作


*/


class Ticket implements Runnable
{
private int ticket = 100;
Object obj = new Object();
boolean flag = true;


public void run()
{
if (flag)
{
while (true)
{
synchronized(this)
{
if (ticket > 0)
{
try {Thread.sleep(10);}catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"........run......"+ticket--);
}
}
}
}
else 
while(true)
show();


}


public synchronized void show()
{
if (ticket > 0)
{
try {Thread.sleep(10);}catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"........show......"+ticket--);
}
}


}


class  ThisIsLock
{
public static void main(String[] args) 
{
Ticket t = new Ticket();


Thread t1 = new Thread(t);
Thread t2 = new Thread(t);


t1.start();
try {Thread.sleep(10);}catch (Exception e){}


t.flag = false;


t2.start();
}
}


五、靜態同步函式的鎖是Class物件
如果同步函式被靜態修飾後,使用的鎖是什麼呢?
不是this,因為靜態方法中也不可以定義this。
靜態進記憶體時,記憶體中沒有本類物件,但是一定有該類對應的位元組碼檔案物件 類名.class 該物件的型別是Class


靜態的同步方法,使用的鎖是該方法所在類的位元組碼檔案物件  類名.class


Demo:
/*
靜態同步函式
*/


class Ticket implements Runnable
{
private static int ticket = 100;
//Object obj = new Object();
boolean flag = true;


public void run()
{
if (flag)
{
while (true)
{
synchronized(Ticket.class)
{
if (ticket > 0)
{
try {Thread.sleep(10);}catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"........run......"+ticket--);
}
}
}
}
else 
while(true)
show();


}


public static synchronized void show()
{
if (ticket > 0)
{
try {Thread.sleep(10);}catch (Exception e){}
System.out.println(Thread.currentThread().getName()+"........show......"+ticket--);
}
}


}


class  StaticMethodDemo
{
public static void main(String[] args) 
{
Ticket t = new Ticket();


Thread t1 = new Thread(t);
Thread t2 = new Thread(t);


t1.start();
try {Thread.sleep(10);}catch (Exception e){}


t.flag = false;


t2.start();
}
}


六、生產者消費者
對於多個生產者和消費者,為什麼要定義while判斷標記?
原因:讓被喚醒的執行緒再一次判斷標記


為什麼定義notifyAll?
因為需要喚醒對方執行緒,只用notify,容易出現只喚醒本方執行緒的情況,導致程式中的所有執行緒都等待。




class Resource
{
private String name;
private int count = 1;
private boolean flag = false;


public synchronized void set(String name)
{
while(flag)
try
{
wait();
}
catch (Exception e)
{
}
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生產者........"+this.name);
flag = true;
this.notifyAll();//this.notify();只是用於兩個執行緒,不適用於多執行緒。而且要用while迴圈多次判斷flag
}


public synchronized void out()
{
while(!flag)
try
{
wait();
}
catch (Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"........消費者......."+this.name);
flag = false;
this.notifyAll();
}


}




class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}


public void run()
{
while (true)
{
r.set("商品");
}
}
}




class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}


public void run()
{
while (true)
{
r.out();
}
}
}


class ProducerConsumerDemo 
{
public static void main(String[] args) 
{
Resource r =new Resource();


Producer p = new Producer(r);
Consumer c = new Consumer(r);


Thread t1 = new Thread(p);
Thread t2 = new Thread(c);


t1.start();
t2.start();
}
}


JDK1.5中提供了多執行緒升級解決方案:
將同步synchronized替換成lock操作
將Object中的wait,notify,notifyAll替換了Condition物件。該物件可用lock鎖進行獲取


/*
JDK5.0升級版的生產者消費者問題


顯式的lock
只喚醒對方執行緒
*/


import java.util.concurrent.locks.*;






class Resource
{
private String name;
private int count = 1;
private boolean flag = false;


private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();






public void set(String name) throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生產者........"+this.name);
flag = true;
condition_con.signal();

}
finally
{
lock.unlock();
}

}


public synchronized void out() throws InterruptedException
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"........消費者......."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();
}


}


}




class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}


public void run()
{

while (true)
{
try
{
r.set("商品");
}
catch (InterruptedException e)
{
}
}
}
}




class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}


public void run()
{
while (true)
{
try
{
r.out();
}
catch (InterruptedException e)
{
}

}
}
}


class ProducerConsumerDemo2
{
public static void main(String[] args) 
{
Resource r =new Resource();


Producer p = new Producer(r);
Consumer c = new Consumer(r);


Thread t1 = new Thread(p);
Thread t2 = new Thread(c);


t1.start();
t2.start();
}
}