內容:Java黑馬就業班 P307-334
文章目錄
java程序屬於搶佔式調度,那個線程的優先級高,那個線程優先執行:同一個優先級,隨機選擇一個執行。
每個新線程都會開闢一個新的棧空間來執行run方法,cpu可以選擇線程執行。
創建多線程程序的方式
創建多線程程序的第一種方式
創建Thread類的子類
java.lang.Thread類是描述線程的類,想要實現多線程程序,就必須繼承Thread類
實現步驟:
1.創建一個Thread類的子類
2.在Thread類的子類種重寫Thread類中的run方法,設置線程任務(開啓線程要做什麼)
//這裏創建了一個叫MyThread的子類
public class MyThread extends Thread{
@Override
public void run(){
......
}
}
3.創建Thread類中的子類對象
4.調用Thread類中的方法,開始新的線程,執行run方法
public class Main{
public static void main(String []args){
MyThread mt=new MyThread();
mt.start();
}
}
void start()使該線程開始執行;Java虛擬機調用該線程的run方法。
結果是兩個線程併發地運行;當前線程和另一個線程。
多次啓動一個線程是非法的,特別是當線程已經結束執行後,不能再重新啓動。
創建多線程程序的第二種方式
實現Runnable接口
java.lang.Runnable,Runnable接口應該由那些打算通過某一線程執行其實例的類來實現。類必須定義一個稱爲run的無參數方法
java.lang.Thread類的構造方法
Thread(Runnable target)分配新的Thread對象。
Thread(Runnable target,String name)分配新的Thread對象。
實現步驟:
1.創建一個Runnable接口的實現類
2.在實現類中重寫 RunnabLe接口的run方法,設置線程
public class RunnableImp1 implements Runnable{
@Override
public void run(){
......
}
}
3.創建一個 Runnable接口的實現類對象
4.創建 Thread類對象,構造方法中傳遞Runnable接口的實現類對象
5.調用 Thread類中的 start方法,開啓新的線程執行run方法
public class Main{
public static void main(String []args){
RunnableImp1 run=new RunnableImp1();
Thread t=new Thread(run);
t.start();
}
}
兩種方式的區別
實現了Runnable接口創建多線程程序的好處:
1.避免了單繼承的侷限性
(1)一個類只能繼承一個類(一個人只能有一個親爹)類繼承了 Thread類就不能繼承其他的類
(2)實現了Runnable接口,還可以繼承其他的類,實現其他的接口
2.增強了程序的擴展性,降低了程序的耦合性(解耦)
(1)實現 Runnable接口的方式,把設置線程任務和開啓新線程進行了分離(解耦)
(2)實現類中,重寫了run方法:用來設置線程任務
(3)創建Thread類對象,調用 start方法:用來開啓新線程
使用匿名內部類實現線程的創建
匿名:沒有名字
內部類:寫在其他類內部的類
匿名內類作用:簡化代碼
把子類繼承父類,重寫父類的方法,創建子類對象合一步完成
把實現類實現類接口,重寫接口中的方法,創建實現類對象合成一步完成
匿名內部類的最終產物:子類/實現類對象,而這個類沒有名字
//格式
new 父類/接口(){
重複父類/接口中的方法
};
public class Main{
public static void main(String []args){
//第一種 線程父類是Thread
new Thread(){
@Override
public void run(){
......
}
}.start();
//第二種 線程接口Runnable
Runnable r=new Runnable(){
@Override
public void run(){
......
}
};
new Thread(r).start();
//第三種 在第二種的基礎上繼續匿名
new Thread(new Runnable(){
@Override
public void run(){
......
}
}).start();
}
}
線程同步機制
一、同步代碼塊
同步代碼塊:
synchronized可以用於方法鐘的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖:
對象的同步鎖只是一個概念,可以想象爲在對象上標記了一個鎖
1.鎖對象 可以是任意類型
2.多個線程對象 要使用同一把鎖
注意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程只能在外等着(BLOCKED)。
注意:
1.通過代碼塊中的鎖對象,可以使用任意的對象
2.但是必須保證多個線程使用的鎖對象是同一個
3.鎖對象作用:把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行
二、同步方法
同步方法:
使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等着。
public synchronized void method(){
可能會產生線程安全問題的代碼
}
同步鎖是誰?
對於非 static方法同步鎖就是this。
對於 static方法我們使用當前方法所在類的字節碼對象(類名.class)
使用步驟:
1.把訪問了共享數據的代碼抽取出來,放到一個方法中
2.在方法上添加 synchronized修飾符
三、Lock鎖
java.util.concurrent.locks.Lock機制提供了比synchronized代碼塊和 synchronized方法更廣泛的鎖定操作。同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。
Lock接口中的方法:
void lock() 獲取鎖
void unlock() 釋放鎖
java.util.concurrent.locks.ReentrantLock implements lock 接口
使用步驟:
1.在成員位置創建一個 ReentrantLock對象
Lock l=new ReentrantLock ();
2.在可能會出現安全問題的代碼前調用Lock接口中的方法Lock獲取鎖
l.lock();
在可能會出現安全問題的代碼後調用Loc接口中的方法 unLock釋放鎖
l.unlock();
線程狀態
線程概述圖
Timed Waiting(計時等待)
當我們調用了sleep方法之後,當前執行的線程就進入到”休眠狀態“,其實就是所謂的 Timed Waiting
Timed blocked(鎖阻塞)
沒爭取到鎖對象就是鎖阻塞狀態
WAITING(無限等待狀態)
直接上demo
package demo1;
public class demo1wait {
/*
* 等待喚醒案例:線程之間的通信
* 創建一個顧客線程(消費者):告知老闆要的包子的種類和數量,調用wait方法,放棄cpu的執行,進入到WAITING狀態
* 創建一個老闆線程(生產者):花了5秒做包子,做好包子之後,調用notify方法,喚醒顧客吃包子
*
* 注意:
* 顧客和老闆線程必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行
* 同步使用的鎖對象必須保證唯一
* 只有鎖對象才能調用wait方法和notify方法
*/
public static void main(String[] args) {
//創建鎖對象,保證唯一
final Object obj=new Object();
//創建一個顧客線程(消費者)
new Thread(){
@Override
public void run(){
//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
synchronized(obj){
System.out.println("我要兩個菜包!");
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("你家的菜包子真好吃");
}
}
}.start();
//創建一個老闆線程(生產者)
new Thread(){
@Override
public void run(){
try {
sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
synchronized(obj){
System.out.println("老闆:您要的兩個菜包子來啦!");
obj.notify();
}
}
}.start();
}
}
如果此時有兩個顧客同時來買包子,老闆應該可以一起做,一起給,那就不能用notify方法了,用notifyAll方法。上demo
package demo1;
public class demo1wait {
/*
* 等待喚醒案例:線程之間的通信
* 創建一個顧客線程(消費者):告知老闆要的包子的種類和數量,調用wait方法,放棄cpu的執行,進入到WAITING狀態
* 創建一個老闆線程(生產者):花了5秒做包子,做好包子之後,調用notify方法,喚醒顧客吃包子
*
* 注意:
* 顧客和老闆線程必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行
* 同步使用的鎖對象必須保證唯一
* 只有鎖對象才能調用wait方法和notify方法
*/
public static void main(String[] args) {
//創建鎖對象,保證唯一
final Object obj=new Object();
//創建一個顧客線程(消費者)
new Thread(){
@Override
public void run(){
while(true){
synchronized(obj){
System.out.println("顧客1:我要兩個菜包!");
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("顧客1:你家的菜包子真好吃");
System.out.println("--------------------------");
}
}
//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
}
}.start();
new Thread(){
@Override
public void run(){
while(true){
synchronized(obj){
System.out.println("顧客2:我要兩個菜包!");
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("顧客2:你家的菜包子真好吃");
System.out.println("--------------------------");
}
}
//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
}
}.start();
//創建一個老闆線程(生產者)
new Thread(){
@Override
public void run(){
while(true){
try {
sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
synchronized(obj){
System.out.println("老闆:您要的兩個菜包子來啦!");
// obj.notify();
obj.notifyAll();
}
}
}
}.start();
}
}
線程池
一、線程池概述
線程池是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。
在JDK1.5之後,JDK內置了線程池,可直接使用
線程池是個容器,那就是用集合來裝的(ArrayList,HashSet,LinkedList,HashMap)
當程序第一次啓動的時候創建多個線程保存到一個集合中;當我們想要使用線程的時候就可以從集合中取出來線程使用。
Thread t=list.remove();//返回被移除的元素,線程只能被一個任務使用
//以LinkedList爲例
Thread t=linked.removeFirst();
當我們使用完畢線程,需要把線程歸還給線程池
list.add(t);
//以LinkedList爲例
linked.addLast(t);
合理利用線程池的好處
1.降低資源消耗。減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個。任務
2.提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
3.提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
二、線程池的使用
java.util.concrrent.Executors:線程池的工廠類,用來生成線程池
Executors類中的靜態方法:
static ExecutorService newFixedThreadPool(int nThreads) 創建一個可重用固定線程數的線程池
參數:
int nThreads:創建線程池中包含的線程數量
返回值:
ExecutorService接口,返回的是ExecutorService接口的實現類對象,我們可以使用ExecutorService接口接收(面向接口編程)
java.util.concurrent.ExecutorService:線程池接口
用來從線程池中獲取線程,調用start方法,執行線程任務
submit(Runnable task)提交一個Runnable任務用於執行
關閉/銷燬線程池的方法
void shutdown()
線程池的使用步驟:
1.使用線程池的工廠類Executors裏邊提供的靜態方法 newFixedThreadpool生產一個指定線程數量的線程池
2.創建一個類,實現 Runnable接口,重寫run方法,設置線程任務
3.調用ExecutorService中的方法 submit,傳遞線程任務(實現類),開啓線程,執行run方法調用
4.ExecutonService中的方法 shutdown銷燬線程池(不建議執行)
demo
Runnable接口實現類
package demo1;
public class RunnableImp implements Runnable{
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"正在運行");
}
}
主函數類
package demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class demo1wait {
public static void main(String[] args) {
//開啓3個線程的線程池
ExecutorService es=Executors.newFixedThreadPool(3);
es.submit(new RunnableImp());
es.submit(new RunnableImp());
es.submit(new RunnableImp());
es.submit(new RunnableImp());
es.submit(new RunnableImp());
es.submit(new RunnableImp());
//es.shutdown();
}
}
運行結果