------ Java教育訓練、Android教育訓練、iOS教育訓練、.Net教育訓練、期待與您交流! -------
程序: 程序:是一個正在執行的程式 每個程序執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元
線程:
線程就是程序中的一個獨立的控制單元,線程在控制着程序的執行。一個程序中至少有一個線程。
多線程: 在java虛拟機啟動的時候會有一個java.exe的執行程式,也就是一個程序。
該程序中至少有一個線程負責java程式的執行。而且這個線程運作的代碼存在于main方法中。該線程稱之為主線程。
JVM啟動除了執行一個主線程,還有負責垃圾回收機制的線程。 像種在一個程序中有多個線程執行的方式,就叫做多線程。
多線程的好處: 多線程的出現能讓程式産生同時運作效果。可以提高程式執行效率。
就我們常見的,在電腦上,即能在記事本上打字,同時也能聽音樂。這個就是多線程的應用。又比如360安全衛士,當我們在清理垃圾的時候,同時也能掃描系統漏洞。這同僚是多線程的應用有。
1、如何在自定義的代碼中,自定義一個線程呢?
通過對API的查找,java已經提供了對線程這類事物的描述。就是Thread類。
建立線程的第一種方式:繼承Thread類
步驟:
1、定義類繼承Thread。
2、複寫Thread類中的run 方法
目的:将自定義代碼存儲在run方法中,讓線程運作。
3、調用線程的start方法。該方法有兩個作用:啟動線程,調用run方法
如下:
class Test extends Thread
{
//複寫父類中的run方法
public void run()
{
for(int i=0;i<30;i++)
{
System.out.println(Thread.currentThread().getName()+" run .."+i);
}
}
}
class ThreadDemo
{
public static void main(String[] agrs)
{
Test t1=new Test();
t1.start();//開啟線程,并調用run方法
Test t2=new Test();
t2.start();
for(int i=0;i<60;i++)
{
System.out.println("main run::"+i);
}
}
}
運作多次後,發現運作結果每一次都不同。因為多個線程都在擷取CPU的執行權。CPU執行到誰,誰就運作。
明确一點,在某一個時刻,隻能有一個程式在運作。(多核除外) cpu在做着快速的切換,以達到看上去是同時運作的效果。 我們可以形象地把多線程的運作行為看作在互相搶奪CPU的執行權。
這就是多線程的一個特性:随機性。輪到誰,誰執行。
為什麼要覆寫run方法呢?
Thread 類用于描述線程,該類就定義了一個功能,用于存儲線程要運作的代碼,該存儲功能就是run方法。
也就是說Thread類中的run 方法,用于存儲線程要運作的代碼。 我們要利用線程運作我們自己的代碼,是以我們也要将代碼存放在我們自己的run方法中,是以可以直接調用父類中的run方法,并将其複寫。
線程都有自己預設的名稱。
Thread-編号 該編号從0開始
static Thread currentThread():擷取目前線程對象
getName():擷取線程名稱
設定線程名稱:setName或者構造函數
建立線程的第二種方式:實作 Runnable接口
步驟:
1、定義類實作Runnable接口 2、覆寫Runnable接口中的run方法。 将線程要運作的代碼存放在run方法中 3、通過Thread類建立線程對象。 4、将Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。 為什麼要将Runnable接口的子類對象傳遞給Thread的構造函數。 因為,自定義的run方法所屬的對象是Runnable接口 的子類對象。 是以要讓線程去執行指定對象的run方法。 就必須明确該run方法所屬對象。 5、調用Thread類的start方法開戶線程并調用Runnable接口子類的Run方法
下面用一個賣票的小程式來說明:
//賣票線程
class Ticket implements Runnable
{
int num=100;
Object obj=new Object();
public void run()
{
while(true)
{
if(num>0)
{
System.out.println(Thread.currentThread().getName()+"Sale:"+num--);
}
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Ticket t=new Ticket();//建立一個對象,然後再開始四個線程,模拟四個視窗賣票
Thread th1=new Thread(t);//這裡是用到了多态 ,傳遞的是Runnable類型或其子類的對象
Thread th2=new Thread(t);
Thread th3=new Thread(t);
Thread th4=new Thread(t);
th1.start();
th2.start();
th3.start();
th4.start();
}
}
通過實作Runnable接口方式建立線程的好處: 避免了單繼承的局限性。(當一個類本身就有一個父類,則不能再繼承Thread類)
在定義線程時,建議使用實作方式。
差別:
繼承Thread:線程代碼存放在Thread子類的run方法中。
實作Runnable,線程代碼存放在接口的子類的run方法中。
多線程中的安全問題
還是那個賣票程式,再看代碼:
<span style="font-size:14px;">//賣票線程
class Ticket implements Runnable
{
int num=100;
Object obj=new Object();
public void run()
{
while(true)
{
if(num>0)
{
//在此處sleep() 10毫秒,模拟CPU運作到此處的時候,将執行權交給其他程序
try{Thread.sleep(10);} catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"Sale:"+num--);
}
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Ticket t=new Ticket();
Thread th1=new Thread(t);
Thread th2=new Thread(t);
Thread th3=new Thread(t);
Thread th4=new Thread(t);
th1.start();
th2.start();
th3.start();
th4.start();
}
}</span>
結果:
從結果中可以看到,票号出現了負數和0,這明顯是不符合常理的。 造成這情況,是因為多線程的運作出現了安全問題
問題原因:當多條語句在操作同一個線程共享資料時,一個線程對多條語句隻執行了一部分,還沒執行完,另一個線程參與進來執 行,導緻共享資料的錯誤。
解決辦法:對多條操作共享資料的語句,隻能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。
1、java 對于多線程的安全問題提供了專業的解決方式,就是同步代碼塊
synchronized(對象)
{
需要被同步的代碼
}
對象如同鎖。持有鎖的線程可以在同步中執行。沒有持有鎖的線程即使擷取了CPU的執行權,也進不去,因為沒有鎖。
同步的前提:
1、必須要有兩個或兩個以上的線程
2、必須是多個線程使用同一個鎖
必須保證同步中至少有一個線程在運作。
好處:解決了多線程的安全問題
弊端:多個線程都需要判斷鎖,較為消耗資源。
同步代碼塊的使用,如下:
class Ticket implements Runnable
{
int num=100;
Object obj=new Object();
public void run()
{
while(true)
{
//同步
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);} catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"Sale:"+num--);
}
}
}
}
}
如上,同步代碼塊,關鍵字synchronized,這裡的鎖為任一對象。
2、解決線程安全問題的第二種方式。同步函數
格式:在函數上加上synchronized修飾符
那麼,同步函數用的是哪一個鎖呢? 函數需要被對象調用。那麼函數都有一個所屬對象引用。就是this。 是以同步函數使用的鎖是this
如上面的代碼片段可以直接用同步代碼塊的方式完成,如下:
//賣票線程
class Ticket implements Runnable
{
int num=100;
Object obj=new Object();
public void run()
{
while(true)
{
this.Sale();
/*
//同步代碼塊
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);} catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"Sale:"+num--);
}
}
*/
}
}
public synchronized void Sale()
{
if(num>0)
{
try{Thread.sleep(10);} catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"Sale:"+num--);
}
}
}
那麼問題來了,如何确認哪些代碼需要被同步呢?如何尋找多線程中的安全問題呢? a,明确哪些代碼是多線程運作代碼。 b,明确共享資料。
c,明确多線程運作代碼中哪些語句是操作共享資料的。
靜态同步函數
如果同步函數被靜态修飾後,使用的鎖是什麼呢?
通過驗證,發現不再是this。因為靜态方法中也不可以定義this。
靜态進記憶體時,記憶體中沒有本類對象,但是一定有該類對應的位元組碼檔案對象。
類名.class 該對象的類型是 Class
驗證靜态同步函數的鎖為 類名.class
靜态的同步函數,使用的鎖是該方法所在類的位元組碼檔案對象。 類名.class
說到多線程的安全問題,想到之前的單例設計模式。 單例設計模式的特點就是對象的唯一性。當多線程通路單例設計模式中的懶漢式的時候。因為懶漢式中有一個判斷:if(s==null) s=new Single(); 這裡是兩行代碼,當多線程運作到第一句發生CPU轉移執行權的時候,會發生安全問題,這樣,對象就可能不是一個,而是多個了,是以根據多線程的安全問題,我們可以對懶漢式進行同步操作,以解決懶漢式中的安全問題。如下:
class Single
{
// 構造函數私有化,就不能建立類的對象
private Single(){}
private static Single s=null;
//每次都要判斷鎖,比較低效
public static synchronized Single getInstance()
{
if(s==null)
s=new Single();
return s;
}
}
但是,每次線程通路getInstance()方法時,都有一個判斷鎖的動作,這樣做效率比較低。是以又用同步代碼塊對懶漢式進行了優化:
class Single
{
// 構造函數私有化,就不能建立類的對象
private Single(){}
private static Single s=null;
public static Single getInstance()
{
//使用雙重判斷
if(s==null)
{
//鎖為該類所屬位元組碼檔案對象
synchronized(Single.class)
{
if(s==null)
s=new Single();
return s;
}
}
}
}
這裡使用了雙重判斷,以免每次通路該函數時,在函數中要進行判斷鎖的操作,影響效率,使後面的通路隻需要判斷s是否為空即可。
死鎖 死鎖是什麼?死鎖即是同步中嵌套同步。如下例:
class Test implements Runnable
{
boolean flag;
Test(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.obja)
{
System.out.println(" if obja");
synchronized(MyLock.objb)
{
System.out.println(" if objb");
}
}
}
else
{
synchronized(MyLock.objb)
{
System.out.println(" else objb");
synchronized(MyLock.obja)
{
System.out.println(" else obja");
}
}
}
}
}
class MyLock
{
static Object obja=new Object();
static Object objb=new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Test te1=new Test(true);
Test te2=new Test(false);
Thread t1=new Thread(te1);
Thread t2=new Thread(te2);
t1.start();
t2.start();
}
}
結果:
線程間通信
線程間通信,即是多個線程操作同一資源,但操作的内容不同。 舉例,A和B去銀行辦個人業務,其中A是存錢,B是取錢。操作的都是銀行的金庫。
如下例,兩個線程操作一個共同資源,一個向資源内指派,一個從資源中取值。希望兩個線程互動,存一個,取一個
<span style="font-size:14px;">//共同資源
class Res
{
boolean flag=false;
String name;
String sex;
}
//輸入類,實作Runnable接口
class Input implements Runnable
{
Res r;
Input(Res r)
{
this.r=r;
}
public void run()
{
int x=0;
while(true)
{
synchronized(r)
{
if(r.flag==false)
{
if(x==0)
{
r.name="zhangsa";
r.sex="man";
}
else
{
r.name="李四";
r.sex="女";
}
r.flag=true;
}
}
x=(x+1)%2;
}
}
}
//輸出類
class Output implements Runnable
{
Res r;
Output(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
synchronized(r)
{
if(r.flag)
{
System.out.println(r.name+"::::"+r.sex);
r.flag=false;
}
}
}
}
}
class InputOutputDemo
{
public static void main(String[] args)
{
//建立一個資源對象
Res r=new Res();
Input in=new Input(r);
Output out=new Output(r);
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}</span>
結果:
上述代碼使用線程等待喚醒機制來優化:
//資源
class Res
{
boolean flag=false; //辨別,用于判斷目前線程是應做存取操作還是等待
private String name;
private String sex;
//存入操作
public synchronized void set(String name,String sex)
{
if(flag) //若已存入資源,則線程等待
try{this.wait();}catch(Exception e){} //線程等待。
this.name=name;
this.sex=sex;
this.flag=true;
//喚醒線程池中的其他等待線程
this.notify();
}
//取出操作
public synchronized void out()
{
if(!flag)
try{this.wait();} catch(Exception e){}
System.out.println(name+":::"+sex);
this.flag=false;
this.notify();
}
}
//輸入類,實作Runnable接口
class Input implements Runnable
{
Res r;
Input(Res r)
{
this.r=r;
}
public void run()
{
int x=0;
while(true)
{
if(x==0)
r.set("zhangsan","man");
else
r.set("李四","女");
x=(x+1)%2;
}
}
}
//輸出類
class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class InputOutputDemo1
{
public static void main(String[] args)
{
//建立一個資源對象
Res r=new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
wait(): notify()
notifyAll();
都使用在同步中,因為要對持有螢幕(鎖)的線程操作。是以要使用在同步中,因為隻有同步才具有鎖
為什麼這些操作線程的方法要定義在Objcect類中呢?
1、因為這些方法在操作同步中線程時,都必須要辨別它們所操作線程持有的鎖。
隻有同一個鎖上的被等待線程,可以被同一個鎖上的notify喚醒,不可以對不同鎖中的線程喚醒。也就是說,等待和喚醒必須是同一個鎖。
2、鎖可以是任意對象,是以可以被任意對象調用的方法定義在Object類中
JDK1.5中提供了多線程更新解決方法
将同步synchronized替換成顯式的Lock操作。 将Object中的wait,notify notifyAll,替換成Condition對象。 該對象可以通過Lock鎖進行擷取.lock.newConditiom
通過下面一個代碼來實作,看看與synchronized有什麼不同:
import java.util.concurrent.locks.*;
class Res
{
private String name;
private int num=1;
boolean flag=false;
//建立Lock接口的子接口,父類引用指向子類對象
private Lock lock=new ReentrantLock();
//建立Condition對象,用來控制生産者的鎖,等待和喚醒本鎖上的線程
private Condition condition_pro =lock.newCondition();
//建立Condition對象,用來控制消費者的鎖,等待和喚醒本鎖上的線程
private Condition condition_con =lock.newCondition();
public void set(String name) throws InterruptedException
{
lock.lock();
//因為使用Conditiom的await方法,會抛出異常
try
{
while(flag)
condition_pro.await();//線程等待,抛出異常
this.name=name+(num++);
System.out.println("生産者::::::"+this.name);
condition_con.signal();//消費者線程喚醒
flag=true;
}
//一定執行的操作:解開鎖
finally
{
lock.unlock();
}
}
public synchronized void out()throws InterruptedException
{
//鎖上鎖
lock.lock();
try
{
while(!flag)
condition_con.await(); //消費者線程等待
System.out.println("消費者: "+name);
condition_pro.signal();//生産者線程喚醒
flag=false;
}
finally{
lock.unlock(); //解開鎖
}
}
}
class Producter implements Runnable
{
Res r;
Producter(Res r)
{
this.r=r;
}
public void run()
{
while(true)
{
try
{
r.set("商品");
}
catch(InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
Res r;
Consumer(Res r)
{
this.r=r;
}
public void run()
{
while(true)
try
{
r.out();
}
catch(InterruptedException e)
{}
}
}
class ProConLockTest
{
public static void main(String[] args)
{
Res r=new Res();
Producter pro=new Producter(r);
Consumer con=new Consumer(r);
Thread t1=new Thread(pro);
Thread t2=new Thread(con);
Thread t3=new Thread(pro);
Thread t4=new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
結果:
停止線程
兩種方式: 1、定義循環結束标記 因為線程運作代碼一般都是循環,隻要控制了循環即可。如下:
class StopThread implements Runnable
{
private boolean flag=true;
public void run()
{
while(flag)
{
System.out.println("aaaaa");
}
}
//改變标志的值
public void chageFlag()
{
flag=false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st=new StopThread();
Thread t1=new Thread(st);
t1.start();
int num=0;
while(true)
{
if(num++==60)
{
st.chageFlag();
break;
}
System.out.println("num="+num);
}
}
}
特殊情況:
當線程處于了當機狀态,就不會讀取到标記,那麼線程就不會結束。當沒有指定的方式讓當機的線程恢複到運作狀态時,這時需要對當機狀态進行清除。強制讓線程恢複到運作狀态中來。這樣就可以操作标記讓線程結束。
2、使用interrupt(中斷)方法 (stop方法已過時)
該方法中結束線程的當機狀态,使線程回到運作狀态中來。
//Thread 類提供該方法 interrupt();
*/
class StopThread implements Runnable
{
private boolean flag=true;
public void run()
{
while(flag)
{
try
{
wait();
}
catch(InterruptedException e)
{
flag=false;
}
System.out.println(Thread.currentThread().getName()+"....");
}
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st=new StopThread();
Thread t1=new Thread(st);
t1.start();
Thread t2=new Thread(st);
t2.start();
int num=0;
while(true)
{
if(num++==60)
{
t1.interrupt();
t2.interrupt();
break;
}
System.out.println("num="+num);
}
System.out.println("over");
}
}
多線程什麼時候用? 當某些代碼需要同時被執行時,就用單獨的線程進行封裝。
如下:
class UseMulThread
{
public static void main(String[] args)
{
/*使用繼承方式(匿名内部類)*/
new Thread(){
public void run()
{
for(int i=0;i<50;i++)
{
System.out.println("我是線程一");
}
}
}.start();
/******使用實作Runnable方式(匿名内部類)*********/
Runnable ra= new Runnable()
{
public void run()
{
for(int j=0;j<40;j++)
{
System.out.println("我是線程二");
}
}
};
new Thread(ra).start();
/***************主線程*********************/
for(int k=0;k<40;k++)
{
System.out.println("我是主線程");
}
}
}
join方法
當A線程執行到了b線程的.join()方法時,A線程就會等待,等B線程都執行完,A線程才會執行。(此時A和其他線程交替運作。)join可以用來臨時加入線程執行。
setPriority()方法用來設定優先級
MAX_PRIORITY 最高優先級10
MIN_PRIORITY 最低優先級1
NORM_PRIORITY 配置設定給線程的預設優先級5
優先級高是擷取CPU機率大一些,優先級高并不一定就先執行。 yield()方法可以暫停目前線程,讓其他線程執行。