天天看點

黑馬程式員_Java基礎(4)--多線程

------- android教育訓練、java教育訓練、期待與您交流! ----------

(day11多線程)**********************************************************************

@@多線程

程序:是一個正在執行中的程式,每一個程序執行都有執行順序,該順序是一個執行路徑,或者叫控制單元。

線程:就是程序中獨立的控制單元,線程在控制着程序的執行。

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

(線程是程式的控制機關或着叫執行路徑。)

java jvm 啟動時候會有一個進行java.exe

該程序中至少一個線程負責java程式的執行。

而且這個線程運作的代碼存在main方法中,該線程稱之為主線程。

jvm啟動至少有兩個線程:一個是主線程,一個是負責垃圾回收機制的線程。

自定義一個線程。

1,繼承Thread類,重寫run方法。

2,實作Runnable接口,重寫run方法。

@@第一種:繼承Thread類,重寫run方法

1,定義類繼承Thread類;class Demo extends Thread

2,重寫Thread類中的run方法:将自定義的代碼存儲在run方法中,讓線程運作。

3,調用start() 方法,使該線程開始執行;Java 虛拟機調用該線程的 run 方法。

Demo d = new Demo();

d.start();

多線程的一個特性:随機性,誰搶到cpu,誰就先執行,至于執行多長時間cpu說的算。

為什麼覆寫run方法?

Thread類用于描述線程。該類就定義了一個功能,用于存儲線程要運作的代碼,該存儲功能就是run方法。

也就是說Thread類中的run方法,用于存儲程式要運作的代碼。

線程運作狀态:

<被建立>--start--<運作>--sleep(time)--<當機>--sleep的time到了--<運作>-

<被建立>--start--<運作>--wait(timeout)--<當機>--notify--<運作>-

<被建立>--start--<運作>--stop()/或run()結束--<消亡>

<被建立>--start--<臨時/阻塞>--<運作>--stop()/或run()結束--<消亡>

被建立:Demo d = new Demo();d.start();

運作:既有執行資格,由有執行權。

臨時/阻塞:具備執行資格,但沒有執行權,執行權在别的線程手上。

當機:放棄了執行資格。

消亡:run()執行完,線程結束。

static void sleep(long millis) :等待millis毫秒。

static Thread currentThread() 傳回對目前正在執行的線程對象的引用。

String getName() 傳回該線程的名稱

設定線程名稱:構造函數或setName()方法

線程名稱:

有自己的預設名稱Thread-0...;

也可以初始化時定義名稱

class Demo extends Thread

{

 Demo(String name)

 {

 super();

 }

 public void run

 {

  for(int x=0;x<100;x++)

  {

  System.out.println((Thread.currentThread()==this)+":"+this.name+x);

  }

 }

}

@@第二種:實作Runnable接口,重寫run()方法。将實作Runnable接口的類對象傳遞給Thread類的構造函數。

大多數情況下,如果隻想重寫 run() 方法,而不重寫其他 Thread 方法,那麼應使用 Runnable 接口。

這很重要,因為除非程式員打算修改或增強類的基本行為,否則不應為該類建立子類。

1,定義類實作接口Runnable,并重寫run方法:class Demo implements Runnable

2,建立Thread對象,并将Runnable的實作類傳遞給Thread類的構造函數

3,調用Thread類的start方法開啟線程。

Demo d = new Demo();

Thread t = new Thread(d);//将d作為實際參數傳遞給Thread類的構造函數

t.start();//開啟線程,并執行d中的run方法。

**實作方式和繼承方式的差別?*******

實作方式的好處:避免了單繼承的局限性。

繼承Thread:線程代碼存放在Thread子類的run方法中。

實作Runnable:線程代碼存在實作接口的類的run方法中。

在定義線程時,建議最好使用實作接口Runnable的方式。

@@多線程的安全問題

安全問題原因:當多條語句在操作同一個線程共享資料時,一個線程對多條語句值執行了一部分,

 還沒有執行完,另一個線程就參與進來,導緻共享資料的錯誤。

安全問題的解決辦法:對多條操作共享資料的語句,隻能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。

java對于多線程的安全問題的解決方式-->同步(同步代碼塊和同步函數)******

@@同步代碼塊

class Demo implements Runnable{

private int tick = 1000;//共享資料

Object obj = new Object();//為同步代碼塊建立一個鎖/對象

public void run(){

不需要同步的代碼;

synchronized(對象/鎖 obj)//同步代碼塊

{

 需要被同步的代碼;

 try{Thread.sleep(100);}catch{InterruptedException ie}

 //不能在函數位置上throws InterruptedException,

 //因為實作的接口沒有抛出任何異常,是以隻能在内部try處理。

}

}

}

同步代碼塊的原理:

對象存在的時候有個标志位,預設為0;

當Thread-0線程進入同步代碼塊時,先判斷該對象的标志位,

    如果為0,表示同步代碼快内部沒有線程,可以進入,是以Thread-0進入該代碼塊,同時對象的标志位改為1;

當Thread-1、2、3...等線程想要進入同步代碼塊時,先判斷該對象的标志位,

    如果為1,表示同步代碼快内部有1個線程,不可以進入,是以Thread-1沒有進入該線程,保證了同步代碼快内的共享資料的安全性;

當Thread-0線程執行完同步代碼塊中的語句時,離開代碼塊,然後同時該對象的标志位變為0,表示同步代碼快内沒有線程,其他線程可以進入;

同步的前提;***************

1,必須要有兩個或者以上的線程。

2,必須是多個線程使用同一個鎖()。//不能同步代碼快用obj鎖,同步函數用this鎖。!!!!****

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

同步的好處:解決了多線程的安全問題。

同步的弊端:多個線程需要判斷鎖,較為消耗資源。

在程式中查找安全問題:

1,明确哪些代碼是多線程運作代碼;

2,明确哪些是共享資料;

3,明确多線程代碼中哪些語句是操作共享資料的。

同步的表現形式:同步代碼塊和同步函數

@@同步函數:

public void run()

{

 不需要同步的代碼;

 show();

 //如果有同步代碼塊,切記要和show()函數用同一個鎖this,否則不安全!!!!!

 //synchronized(this){}

}

public synchronized void show()//預設的鎖是this

{

 (隻是)需要被同步的代碼;//隻把操作共享資料的語句放這!!

 try{Thread.sleep(100);}catch{InterruptedException ie}

 //不能在函數位置上throws InterruptedException,

 //因為實作的接口沒有抛出任何異常,是以隻能在内部try處理。

}

@@鎖

同步代碼塊的鎖: 同一個對象/鎖(可以是this、Demo.class可以是另外的對象)

同步函數的鎖:this  (調用此同步函數的執行個體對象)

 函數需要被對象調用,函數都有一個所屬對象引用,就是this。

靜态同步函數的鎖:類名.class (靜态同步函數所在類 的位元組碼檔案對象)

同步的前提一定要使用同一個鎖!!!!

@@死鎖: 同步中嵌套同步。兩個同步的鎖不一樣!

寫一個死鎖程式:

(day12)*******************************************************************

@@wait,notify,notifyAll,用來操作線程為什麼定義在了Object類中?

1,這些方法存在于同步中,都用在同步中。因為這些方法要對持有螢幕(鎖)的線程操作。

2,使用這些方法必須要辨別其所屬的同步的鎖。鎖.wait() 鎖.notify() 鎖.notifyAll().

3,鎖可以是任意對象,是以任意對象調用的方法一定定義在Object類中。

等待和喚醒必須是同一把鎖。

(wait有異常抛出InterruptedException,notify沒有異常抛出)

wait和sleep的差別?//都有InterruptedException異常。

wait:釋放資源,釋放鎖。

sleep:釋放資源,不釋放鎖。

線程間通信:其實就是多個線程操作同一個資源,但是操作的動作不一樣。

等待喚醒機制:

例1:單個消費者交替運作,和單個生産者,同步;

class Res //專門用來存放和處理 多線程中的共享資料!

{

  private String name;

  private String sex;

  private boolean flag = false;

  public synchronized void set(String name,String sex)

 { 

   if(flag)//set重複執行的時候,flag為真,說明容器有了一個指派,進入wait,等待輸出函數的notify

    //out執行完後轉過來,flag為假,說明容器中沒有指派,然後跳過wait,進入指派程式。

   try{this.wait();}catch (Exception e){}

   this.name = name;

   this.sex = sex;

   flag = true;//指派完成後,flag為真,容器中有值,提醒out函數可以進行輸出。

   this.notify();//喚醒out的notify

 }

 public synchronized void out()

 {

  if(!flag)//set執行完後轉過來,flag真--容器有值--輸出;

    //out重複執行的時候,flag為假--容器無值--進入wait,等待set的notify喚醒。

   try{this.wait();}catch (Exception e){}

  System.out.println(name+"..."+sex);

  flag = false;//flag為假,說明容器中沒有指派

  this.notify();//喚醒set

 }

}

class Input implements Runnable//實作兩種指派方式的交替運作

{

 private Res r ;

 Input(Res r)

 {

  this.r = r;

 }

 public void run()

 {

  int x = 0;//為了讓線程t1交替進行指派。

  while(true)//讓程式多次運作,更容易看出效果。

  { 

    if(x == 0)//為了讓線程t1交替進行指派。

    {

     r.set("wzq","nan");

    }

    else

    {

     r.set("蛋蛋","女");

    }

    x = (x+1)%2;//為了讓線程t1交替進行指派。   

  }  

 }

}

class Output implements Runnable//實作容器中的值,交替輸出。

{

 private Res r ;

 Output(Res r)

 {

  this.r = r;

 }

 public void run()

 {

  while(true)//讓程式多次運作,更容易看出效果。

  {

   r.out();

  }  

 }

}

class InputOutputDemo

{

 public static void main(String[] args) throws Exception

 {

  Res r = new Res();

  Thread t1 = new Thread(new Input(r));

  Thread t2 = new Thread(new Output(r));

  t1.start();  

  t2.start();

 }

}

例2:多個生産者和消費者同步進行;

class Resource

{

 private String thing;

 private int count = 1;

 private boolean flag = false;

 public synchronized void set(String thing)

 {

  while(flag)

   try{this.wait();}catch(InterruptedException e){}

  this.thing = thing;

  this.count = count;

  System.out.println(Thread.currentThread().getName()+"生産"+thing+count);

  flag = true;

  this.notifyAll();

 }

 public synchronized void out()

 {

  while(!flag)

   try{this.wait();}catch(InterruptedException e){}

  System.out.println(Thread.currentThread().getName()+"消費@@@@"+thing+(count++));

  flag = false;

  this.notifyAll();

 }

}

class Producer implements Runnable

{

 private Resource res;

 Producer(Resource res)

 {

  this.res = res;

 }

 public void run()

 { 

  while(true)//讓線程多次運作,容易看出結果。

   res.set("蘋果");

 }

}

class Consumer implements Runnable

{

 private Resource res;

 Consumer(Resource res)

 {

  this.res = res;

 }

 public void run()

 {

  while(true)//讓線程多次運作,容易看出結果。

   res.out();

 }

}

class demo

{

 public static void main(String[] s)

 {

  Resource res = new Resource();

  Producer pro = new Producer(res);

  Consumer con = new Consumer(res);

  Thread t1 = new Thread(pro);

  Thread t2 = new Thread(pro);

  Thread t3 = new Thread(con);

  Thread t4 = new Thread(con);

  t1.start();

  t2.start();

  t3.start();

  t4.start();

 }

}

例3:(JDK1.5後);有一個Object數組,長度100,可以實作多個線程同時進行存儲和取出元素。而且數組可以多次使用。

import java.util.concurrent.locks.*;

class BoundedBuffer

{

 final Lock lock = new ReentrantLock();

 final Condition notFull = lock.newCondition();

 final Condition notEmpty = lock.newCondition();

 final Object[] items = new Object[100];

 int count,putptr,takeptr;

 public void put(Object x)throws InterruptedException

 {

  lock.lock();

  try

  {

   while(count==items.length)

    notFull.await();

   items[putptr] = x;

   System.out.println(Thread.currentThread().getName()+"在向數組中添加第"+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];

   System.out.println(Thread.currentThread().getName()+"在從數組中取出第"+takeptr+"号角标的元素:"+x);

   if(++takeptr == items.length)

    takeptr = 0;

   --count;

   notFull.signal();

   return x;

  }

  finally

  {

   lock.unlock();

  }

 }

}

class Put implements Runnable

{

 BoundedBuffer bb;

 int x = 20;

 Put(BoundedBuffer bb)

 {

  this.bb = bb;

 }

 public void run()//不能抛,隻能在内部進行try

 {

  while(x-->0)

  {

   try

   {

    bb.put(x);

   }

   catch (InterruptedException e)

   {

   }   

  }

 }

}

class Take implements Runnable

{

 BoundedBuffer bb;

 int x = 20;

 Take(BoundedBuffer bb)

 {

  this.bb = bb;

 }

 public void run()//不能抛,隻能在内部進行try

 {

  while(x-->0)

  {

   try

   {

    System.out.println(bb.take());

   }

   catch (InterruptedException e)

   {

   }   

  }

 }

}

class demo

{

 public static void main(String[] s)

 {

  BoundedBuffer bb = new BoundedBuffer();

  Put p = new Put(bb);

  Take t = new Take(bb);

  new Thread(p).start();

  new Thread(p).start();

  new Thread(p).start();

  new Thread(p).start();

  new Thread(t).start();

  new Thread(t).start();

  new Thread(t).start();

 }

}

@@停止線程:原理:讓線程中的run方法可以結束。

1,定義循環結束标記。

特殊情況:

當線程處于了當機狀态。就不會讀取到标記,那麼線程就不會結束。

2,使用interrupt(中斷)方法,(創造一次InterruptedException,然後在catch中進行合理的處理,使線程結束)。

void interrupt()

          中斷線程。中斷 線程的當機狀态 ,然後如果不catch的話線程可能會繼續當機。

如果線程進入了當機狀态,而且也沒有别的線程調用notify對其進行喚醒。

那麼。可以讓線程中斷一次,然後再在catch中進行異常處理,将線程結束。

boolean flag = true;

public synchronized void run()

{

 while(flag)

 {

  try

  {

   wait();//進入當機狀态,而且沒有别的線程将其喚醒。

  }

  catch (InterruptedException e)

  {

   flag = false;

  }

 }

}

public static main(String[] s)

{

 Thread t1 = new Thread(demo);

 t1.start();//進入當機狀态,而且沒有别的線程将其喚醒。

 t1.interrupt();//創造一次InterruptedException異常,然後線程的run方法中對當機狀态進行處理,讓線程正常結束。

}

sleep/wait/join都可以進入當機狀态,都可以用interrupt進行中斷。

@@守護線程:(背景線程,使用者線程)

setDaemon (boolean on)

public final void setDaemon(boolean on)将該線程标記為守護線程或使用者線程。當正在運作的線程都是守護線程時,Java 虛拟機退出。

該方法必須在啟動線程前調用。

t1.setDaemon(true);

t2.setDaemon(true);

t1.start();

t2.start();

當所有的前台線程結束後(main也是前台線程),背景線程自動結束!!!背景依賴前台線程!

一般線程都是前台線程,t.setDaemon(true)才會變為背景程式。

@@t.join:

'誰'碰到了t.join,t就要搶奪'誰'的執行權,然後'誰'被當機了。

 當t運作(期間如果有别的線程也運作,則會和t一起再搶執行權),當t運作完,'誰'才會再繼續執行.

當A線程執行到了B線程的.join()方法時,A就會等待。B線程開始執行(期間有别的線程會和B一起再搶執行權),

 但是隻有當B完全結束後,A才會繼續執行。

B.join相當于A和B交換了争奪執行權的資格。等B執行完後,A才有争奪執行權的資格。

join可以用來臨時加入線程執行。

@@toString()

傳回該線程的字元串表示形式,包括線程名稱、優先級和線程組。

哪個線程(如B)開啟了該線程A,A就屬于B。ThreadGroup(String name)構造一個新線程組。

優先級(1---10,預設為5):優先級越高,執行的機會越多。

setProiority(int x);

 Thread.MAX_PRIORITY最高;Thread.MIN_PRIORITY最低優先級;Thread.NORM_PRIORITY預設

getProiority();

@@Thread.yield()

static void yield()

          暫停目前正在執行的線程對象,并執行其他線程。

開發中多線程的使用情況:但一段代碼需要同時被執行時,(與主函數一起執行,或與其他線程一起執行)

new Thread()//把下面的代碼放進一個線程中,與其他線程同步執行。

{

 public void run()

 {

  for(int x=0; x<100; x++)

  {

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

  }

 }

}.start();//開啟一個線程

for(int x=0; x<100; x++)//主函數中的一個線程

{

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

}

Runnable r  = new Runnable()//把下面的代碼放進一個線程中,與其他線程同步執行。

{

 public void run()

 {

  for(int x=0; x<100; x++)

  {

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

  }

 }

};

new Thread(r).start();//開啟一個線程

------- android教育訓練、 java教育訓練、期待與您交流! ----------