------ Java教育訓練、Android教育訓練、iOS教育訓練、.Net教育訓練、期待與您交流! -------
多線程
一、概念
1、程序:是一個正在執行中的程式。
每一個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
線程:就是程序中的一個獨立的控制單元。線程在控制着程序的執行。一個程序中至少有一個線程。
java VM 啟動時會有一個程序java.exe。該程序中至少有一個線程負責java程式的執行。而且這個線程運作的代碼存在于main方法中。該線程稱之為主線程。
**擴充:其實更細節的說jvm,jvm啟動不止一個線程,還有負責垃圾回收機制的線程。
二、建立多線程
如何在自定義代碼中,自定義一個線程呢?
通過對api的查找,java已經提供了對線程這類事物的描述,就是thread類。
1、建立線程的第一種方式:繼承Thread類。
步驟:
a.定義類繼承thread。
b.複寫Thread類中的run方法。
目的:将自定義代碼存儲在run方法,讓線程運作。
c.調用線程的start方法。
該方法兩個作用:啟動線程,調用run方法。
**多次啟動一個線程是非法的。特别是當線程已經結束執行後,不能再重新啟動。
在程式運作時發現運作結果每一次都不同。
因為多個線程都在擷取CPU的執行權,CPU執行到誰,誰就運作。說明一點,在某一時刻,隻能有一個程式在運作。(多核 除外)。CPU在做着快速的切換,已達到看上去是同時運作的效果。
我們可以形象把多線程的運作行為看成在互相搶奪CPU的執行權。
這就是多線程的一個特性:随機性。誰搶到誰執行,至于執行多長時間,CPU說了算。
2、為什麼要覆run方法呢
Thread類用于描述線程。
該類就定義了一個功能,用于存儲線程要運作的代碼,該存儲功能就是run方法。也就是Thread類中的run方法,用于存儲 線程要運作的代碼。
示例:
class Demo extends Thread
{
public void run()
{
for(int x=0;x<60;x++)
System.out.println("demo run"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//for(int x;x<400;x++)
//System.out.println("hello world!");
Demo d = new Demo();//建立好一個線程。
d.start();//開啟線程并執行該線程的run方法。
//d.run();//僅僅是對象調用方法,而線程建立了,并沒有運作。
for(int x=0;x<60;x++)
System.out.println("hello world!"+x);
}
}
練習:
/*
建立兩個線程,和主線程交替運作。
原來線程都有自己預設的名稱。
thread-編号,編号從0開始。
static Thread currentThraed():擷取目前線程對象。是靜态的
getName():擷取線程名稱。
設定線程名稱:setName或者構造函數。
*/
class Test extends Thread
{
//private String name;
Test(String name)
{
//this.name = name;
super(name);
}
public void run()
{
for(int x=0; x<60;x++)
{
System.out.println((Thread.currentThread()==this)+"...."this.getName()+"..run..."+x);
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Test t1 = new Test("one---");
Test t2 = new Test("two+++");
t1.start();
t2.start();
// t1.run();
// t2.run();
for(int x=0;x<60;x++)
{
System.out.println("main..."+x);
}
}
}
3、建立線程的第二種方式:實作Runnable接口
步驟:
a.定義類實作Runnable接口。
b.覆寫Runnable接口中的run方法。将線程要運作的代碼存放在該run方法中。
c.通過Thread類建立線程對象。
d.将Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。
*為什麼要将Runnable接口的子類對象傳遞給Thread的構造函數?
因為,自定義的run方法所屬的對象是Runnable接口的子類對象。是以要讓線程去指定指定對象的run方法。就必須明 确該run方法所屬對象。
e.調用Thread類的start方法開啟線程并調用Runnable接口子類的run方法。
**(面試必考)實作方式和繼承方式有什麼差別?
實作方式的好處:避免了單繼承的局限性。在定義線程時,建議使用實作方式。
4、兩種方式差別:
繼承Thread:線程代碼存放Thread子類run方法中。
實作Runnable:線程代碼存放在接口的子類的run方法。
示例:
class Ticket implements Runnable//extends Thread
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
/* Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
*/
}
}
對以上示例通過分析,發現,列印出0,-1,-2等錯票。
5、多線程的運作出現了安全問題。
a.問題的原因:
當多條語句在操作同一個線程共享資料時,一個線程對多條語句隻執行了一部分,還沒有執行完,另一個線程參與進來執 行,導緻共享資料的錯誤。
b.解決辦法:
對多條操作共享資料的語句,隻能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
java對于多線程的安全問題提供了專業的解決模式。
就是:同步代碼塊。
synchronized(對象)
{
需要被同步的代碼
}
對象如同鎖。持有鎖的線程可以在同步中執行。沒有持有鎖的線程及時擷取CPU的執行權,也進不去,因為沒有擷取鎖。
6、同步的前提:
a.必須要有兩個或者兩個以上的線程。
b.必須是多個線程使用同一個鎖。
必須保證同步中隻能有一個線程在運作。
好處:解決多線程的安全問題。
弊端:多個線程需要判斷鎖,較為消耗資源,
class Ticket implements Runnable//extends Thread
{
private int tick = 1000;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tick>0)
{
/*
為什麼不能抛異常?因為run()方法複寫的Runnable接口的方法,父類沒有抛異常,子類有異常隻能try
//try{Thread.sleep(10);}catch(Exception //e){}
*/
System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
}
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
練習:
/*
需求:
銀行有一個金庫。
有兩個儲戶分别存300,每次存100,存3次。
目的:該程式是否有安全問題,如果有,如何解決?
如何找到問題:
1.明确那些代碼多線程運作代碼。
2.明确共享資料。
3.明确多線程運作代碼中那些語句是操作共享資料的。
*/
class Bank
{
private int sum;
//Object obj = new Object();
public synchronized void add(int n)//同步函數
{
//synchronized(obj)//同步代碼塊
//{
sum = sum + n;
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("sum="+sum);
/}
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0;x<3;x++)
{
b.add(100);
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
同步函數用的哪一個鎖呢?
函數需要被對象調用,那麼函數都有一個所屬的對象飲用,就是this。
是以同步函數使用的鎖是this。
通過該線程進行驗證。
使用兩個線程來買票。
一個線程在同步代碼塊中。
一個線程在同步函數中。
都在執行買票動作。
*/
class Ticket implements Runnable//extends Thread
{
private int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code:"+tick--);
}
}
}
}
else
while(true)
show();
}
public synchronized void show()//this
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show:"+tick--);
}
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
//try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
//Thread t3 = new Thread(t);
//Thread t4 = new Thread(t);
//t3.start();
//t4.start();
}
}
7、如果同步函數被靜态修飾後,使用的鎖是什麼呢?
通過驗證,發現不再是this。因為靜态方法中也不可以定義this。
靜态進記憶體時,記憶體中沒有本類對象,但是一定有該類對應的位元組碼檔案對象。
類名.class 該對象的類型是class。
靜态的同步方法,使用的鎖是該方法所在類的位元組碼檔案對象,類名.class
應用:單例模式中的展現
//餓漢式。
/*
class Single
{
private static final Single s = new Single;
private Single(){}
public static Single getInstance()
{
returen s;
}
}
*/
//懶漢式
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
//雙重判斷,減少判斷鎖的次數,提高效率
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
return s;
}
}
}
}
死鎖:
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println("if locka");
synchronized(MyLock.lockb)
{
System.out.println("if lockb");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("else lockb");
synchronized(MyLock.locka)
{
System.out.println("else locka");
}
}
}
}
}
class MyLock
{
static Object locka = new Object();
static Object lockb = 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();
}
}
三、多線程間通信
其實就是多個線程在操作同一個資源。但是操作的動作不同。
/*
線程間通訊:
其實就是多個線程在操作同一個資源。
但是操作的動作不同。
*/
class Res
{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex)
{
if(flag)
try{this.wait();}catch(Exception e){}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out()
{
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(name+"...."+sex);
flag = false;
this.notify();
}
}
class Input implements Runnable
{
private Res r;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
if(x==0)
r.set("mike","man");
else
r.set("麗麗","女 女") ;
x=(x+1)%2;
/* synchronized(r)
{
if(r.flag)
try{r.wait();}catch(Exception e){}
if(x==0)
{
r.name ="mike";
r.sex = "man";
}
else
{
r.name ="麗麗";
r.sex = "女 女";
}
x=(x+1)%2;
r.flag= true;
r.notify();
}
*/
}
}
}
class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
/* synchronized(r)
{
if(!r.flag)
try{r.wait();}catch(Exception e){}
System.out.println(r.name+"...."+r.sex);
r.flag = false;
r.notify();
}
*/
}
}
}
class InputOutputDemo
{
public static void main(String[] args)
{
Res r =new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
/*
Input in = new Input(r);
Output out = new Output(r);
Thread t1 =new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
*/
}
}
1、多線程間通訊中常用方法
wait:
ntify();
notifyAll();
都使用在同步中,因為要對持有螢幕(鎖)的線程操作。是以要使用在同步中,因為隻有同步才具有鎖。
*為什麼這些操作線程的方法要定義Object類中呢?
因為這些方法在操作同步中線程時,都必須要辨別它們所操作線程隻有的鎖。隻有同一個鎖上的被等待線程,可以被同一個鎖上的notify喚醒。不可以對不同鎖中的線程喚醒。
也就是說,等待和喚醒必須是同一個鎖。
而鎖可以是任意對象,是以可以被任意對象調用的方法定義Object類中。
示例:
/*
對于多個生産者和消費者。
為什麼要定義while判斷标記。
原因:讓被喚醒的線程再一次判斷标記。
為什麼定義notifyAll,
以為需要喚醒對方線程。
因為隻有notify,容易出現至喚醒本方線程的情況,導緻程式中的所有線程都等待。
*/
class ProduceConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
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();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)
try{wait();}catch(Exception e){}
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"..生産者"+this.name);
flag=true;
this.notifyAll();
}
public synchronized void out()
{
while(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"......消費者"+this.name);
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();
}
}
}
JDK1.5更新版
class ProduceConsumerDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
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();
}
}
/*
JDK1.5中提供了多線程更新解決方案。
将同步synchronized替換成現實Lock操作。
将Object中wait,notify,notifyAll,替換成了Condition對象。
該對象可以Lock鎖進行擷取。
該事例中,實作了本方隻喚醒了對方的操作。
*/
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
//一個鎖Lock可以對應對個Condition。
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"..生産者"+this.name);
flag=true;
condition_con.signal();
}
finally
{
lock.unlock();//釋放鎖的動作一定執行。
}
}
public void out()throws InterruptedException
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"......消費者"+this.name);
flag=false;
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res=res;
}
public void run()
{
while(true)
{
try
{
res.set("商品");
}
catch(InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res=res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch(InterruptedException e)
{
}
}
}
}
2、停止線程
a.定義循環結束标志
因為線程運作代碼一般都是循環,隻要控制了循環即可
b.使用interrupt(中斷)方法。
該方法是結束線程的當機狀态,使線程回到運作狀态中來。
注:stop方法已經過時不在使用。
分析:如何停止線程?
隻有一種,run方法結束。
開啟多線程運作,運作代碼通常是循環結構。
隻要控制住循環,就可以讓run方法結束,也就是線程結束。
*特殊情況:
當線程處于當機狀态。就不會讀取到标記,那麼線程就不會結束。
當沒有指定的方式讓當機的線程恢複到運作狀态時,這時需要對當機進行清除。強制讓線程恢複到運作狀态中來,這樣就 可以操作标記讓線程結束。
Thread類提供該方法 interrupt();
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()
{
while(flag)
{
try{wait();}
catch(InterruptedException e)
{System.out.println(Thread.currentThread().getName()+"...Exception");
flag= false;
}
System.out.println(Thread.currentThread().getName()+"...run");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
//t1.interrupt();
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
}
}
3、其他方法示範
/*
join:
當A線程執行到了B線程的.join()方法時,A就會等待,等B線程都執行完,A才會執行。
join可以用來臨時加入線程執行。
*/
class Demo implements Runnable
{
public void run()
{
for(int x= 0;x<70;x++)
{
System.out.println(Thread.currentThread().getName()+"...."+x);
Thread.yield();//暫停目前正在執行的線程對象。
}
}
}
class JoinDemo
{
public static void main(String[] args)throws Exception
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//t1.setPriority(Thread.MAX_PRIORITY);優先級設定(MAX_PRIORITY最高優先級)
t2.start();
//主線程等待t1結束
t1.join();
for(int x=0;x<80;x++)
{
System.out.println("main....."+x);
}
System.out.println("over");
}
}
練習:
class ThreadTest2
{
public static void main(String[] args)
{
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();
}
}
/*
class Test1 extends Thread
{
public void run()
{
for(int x=0;x<100;x++)
{
System.out.println(Thread.currentThread().getName()+"......"+x);
}
}
}
*/