------- 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教育訓練、期待與您交流! ----------