天天看點

Java筆記3 多線程<1>線程概述、多線程的建立、多線程的安全問題、靜态同步函數的鎖、死鎖

11-01-多線程概述

l  程序定義:程序是一個正在執行中的程式。每個程序都有一個執行順序,該執行順序做

控制單元。

l  線程定義:線程是程序中獨立的一個控制單元。線程在控制着程序的執行。

一個程序中至少有一個線程。

²  多線程存在的意義:多線程的意義在于一個應用程式的多個邏輯單元可以并發地執行。

但是多線程并不意味着多個使用者程序在執行,作業系統也不把每個線程作為獨立的程序來配置設定獨立的系統資源。程序可以建立其子程序,子程序與父程序擁有不同的可執行代碼和資料記憶體空間。而在用于代表應用程式的程序中多個線程共享資料記憶體空間,但保持每個線程擁有獨立的執行堆棧和程式執行上下文。

11-02-建立多線程

l  建立線程有兩種方式:繼承Thread類和實作Runnable接口。

    第一種方式:繼承Thread類。

步驟:

1.定義繼承 Thread類。

2.複寫Thread類中的run方法。

3.調用線程的start方法。

代碼示例:

class Demo extends Thread//繼承1.Thread類

{

public voidrun()//2.複寫run方法

{

for(int i=0;i<60;i++)

{

System.out.println(“Demo run--”+i);

}

}

}

public class ThreadDemo

{

public staticvoid main(String[] args)

{

Demo d = new Demo();

d.start();//3.開啟線程并執行run方法

//d.run();//僅僅是對象調用方法,而線程建立了并沒有執行。

}

for(inti=0;i<60;i++)

{

System.out.println(“main--”+i);

}

}

多次運作後每一次運作結果都不一樣。

原因:多個線程都擷取到cpu的執行權,cpu執行到誰,誰就運作。在某一時刻,隻有一個程式在運作(多核除外),cpu做着快速的切換,已達到看上去是同時運作的效果,我們可以形象地把多線程的運作行為看成是在互相搶奪cpu的執行權。

²  結論:多線程的一個特點:随機性。誰搶到誰就執行,至于執行多長時間,取決于cpu。

11-03-線程run和start特點

l  線程run和start的特點

run方法的特點:用于儲存線程要運作的代碼。

start方法的特點:啟動線程并執行該線程的run方法(在啟動線程後不一定競争到cpu的執行權)。

11-08-建立線程-實作Runnable接口

l  建立線程的第二種方法:實作Runnable接口

步驟:

1.定義類實作Runnable接口。

2.覆寫Runnable接口中的run方法(将線程要運作的代碼存放在run方法中)。

3.建立Runnable接口的子類對象。

4.将Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。

5.調用Thread類的start方法開啟并調用Runnable接口。

代碼示例:

class Ticket implements Runnable//1.實作Runnable接口

{

private int tick= 100;

public voidrun()//2.重寫run方法

{

while(true)

{

if(tick>0)

{

System.out.println(Thread.currentThread().getName()+”...sale”+tick--);

}

}

}

}

class TicketDemo

{

public staticvoid main(String[] args)

{

Ticket t = new Ticket();//3.建立Runnable接口的子類對象

//4.将Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。

thread t1 = new Thread(t);

thread t2 = new Thread(t);

thread t3 = new Thread(t);

thread t4 = new Thread(t);

//5.調用Thread類的start方法開啟線程并調用Runnable接口子類中的run方法

t1.start();

t2.start();

t3start();

t4.start();

}

}

l  實作Runnable接口的好處:避免了單繼承的局限性,适合于資源共享。

11-09-多線程的安全問題

通過對11-08代碼示例運作結果的分析,出現了列印出0,-1,-2等錯票。

11-08示例代碼運作結果分析:

(1)運作run方法有四個線程t1、t2、t3、t4。首先t1擷取cpu執行權。

(2)當tick = 1時,剛判斷完,t1卧倒(t1具備執行資格,但是此刻cpu執行權被其他線程搶走了或是切換到其他程式去了),t1并沒有執行if語句後面代碼塊中的語句。接着t2獲得了cpu執行權,這時的tick = 1,剛判斷完,t2也卧倒。t3、t4如同上述情況也處于卧倒狀态。

(3)某一時刻,t1擷取到了cpu執行權,執行if語句代碼塊,tick--,tick = 0;

(4)按理說tick = 0,if語句代碼塊就不在執行,但是t2、t3、t4已經過判斷,有執行if語句代碼塊的資格。t1運作完畢,t2、t3、t4不在經過if語句判斷,t2擷取cpu執行權,執行if語句代碼塊,列印0号票,tick--;t2運作完畢,t3擷取cpu執行權,列印-1号票,tick--;同理,t4運作列印-2号票。

l  問題的原因?

當多條語句在操作同一個線程共享資料時,一個線程對多條語句隻執行了一部分,還沒執行完,另一個線程就參與進來執行,導緻共享資料的錯誤。

l  解決方法:

對多條操作共享資料的語句,隻能讓一個線程都執行完。在執行過程中,其他線程不可以參與進來。

l  synchronized

同步的前提:

1.必須要有多個(兩個或兩個以上)線程。

2.必須是多個線程使用同一個鎖。(注意:不同線程使用不同鎖會出現線程安全問題)

3.必須保證同步中隻有一個線程在運作。

synchronized的兩種表現形式:

1.同步代碼塊。(對象鎖的運用要準确,要不然會出現線程安全問題)

2.同步函數。(經過視訊展示,同步函數要運用到位,要不然達不到多線程的目的,因為同步函數一次隻能進入一個線程)

同步代碼塊示例:

synchronized(對象)

{

需要同步的代碼塊;

}

同步函數示例:

public synchronized int add(int a){

//需要同步的函數代碼

}

²  synchronized的利與弊:

利:解決多線程的安全問題。

弊:多線程需要判斷鎖,較為消耗資源。

²  小收獲:繼承接口的run方法,調用sleep()等方法時隻能try...catch,不能抛異常。因為接口中的run方法未抛異常。

11-12-多線程-同步函數的鎖是this

l  同步函數用的是哪一個鎖呢?

函數需要被對象調用,那麼函數都有一個所屬對象的引用,就是this。是以同步函數使用的鎖是this。

如果一個類裡面中既有同步函數又有同步代碼塊,示例如下:

class Ticket implements Runnable

{

private int tick= 100;

object obj = newObject();

boolean flag =true;

public voidrun()

{

if(flag)

{

while(true)

{

//如果synchronized的參數是obj而不是this,在兩個或兩個以上的線程執行過

//程中就會出現線程安全問題:列印0号票。

synchronized(obj)//<1>

{

if(tick>0)

{

System.out.println(Thread.currentThread().getName()+”...code”+tick--);

}

}

}

}

else

while(true)

{

show();//或是this.show()

}

}

publicsynchronized void show()//<2>

{

if(tick>0)

{

System.out.println(Thread.currentThread().getName()+”...show...”+tick--);

}

}

public staticvoid main(String[] args)

{

Ticket t = new Ticket();

Thread t1 = new Thread(t);

Thread t1 = new Thread(t);

t1.start();

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

t2.start();

t.flag = false;

t2.start();

}

}

運作結果表明:在有非靜态的同步函數和同步代碼塊的類中,隻有同步代碼塊中的對象鎖為this時,才不會列印出錯票的情況。

11-13-多線程-靜态同步函數的鎖是class對象

l  如果同步函數被靜态修飾符修飾後,使用的鎖是什麼的呢?

通過驗證,發現不是this,因為靜态方法中不可以定義this。

靜态進記憶體時,記憶體中沒有本類對象,但是一定有該類對應的位元組碼檔案對象。就是

類名.class,該對象的類型是Class。

代碼示例:

如果将11-12示例代碼中的<2>處改為:public static synchronized voidshow(){...}

那麼<1>處應該改為:synchronized(Ticket.class){...}

要不然對象鎖不一緻會出現線程安全問題:列印出0号票。

11-15-多線程-死鎖

死鎖是這樣一種情形:多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞,是以程式不可能正常終止。        導緻死鎖的根源在于不适當地運用“synchronized”關鍵字來管理線程對特定對象的通路,使同步中嵌套同步。

l  死鎖代碼舉例:

class Test implements Runnable{

    private boolean flag;

    Test(boolean flag){

       this.flag = flag;

    }

    public void run(){

       if(flag){

           while(true){

              synchronized(MyLock.locka){

                  System.out.println(Thread.currentThread().getName()+"...if locka ");

                  synchronized(MyLock.lockb){

                     System.out.println(Thread.currentThread().getName()+"..if lockb");              

                  }

              }

           }

       }

        else{

           while(true){

              synchronized(MyLock.lockb){

                  System.out.println(Thread.currentThread().getName()+"..else lockb");

                  synchronized(MyLock.locka){

                     System.out.println(Thread.currentThread().getName()+".....else locka");

                  }

              }

           }

       }

    }

}

class MyLock{

    static Objectlocka =new Object();

    static Objectlockb =new Object();

}

class  DeadLockTest{

    public static void main(String[] args) {

       Thread t1 = new Thread(new Test(true));

       Thread t2 = new Thread(new Test(false));

       t1.start();

       t2.start();

    }

}

總結:采用多線程能充分利用CPU資源,提高代碼的運作效率。但是在采用多線程的同時必須解決好線程的安全問題:代碼同步,避免死鎖的出現。