十一、多線程
1、系統結構圖(xmind)
2、tips
——1.多線程概述
JVM啓動時啓動了多條線程,至少有兩個線程可以分析的出來:
1.執行main函數的線程,該線程的任務代碼都定義在main函數中
2.負責垃圾回收的線程。
System類的gc方法告訴垃圾回收器調用finalize方法,但不一定立即執行。
——2.繼承Thread類
創建線程的目的就是爲了開啓一條執行路徑,去運行指定的代碼和其他代碼實現同時運行,而運行的指定代碼就是這個執行路徑的任務,jvm創建的主線程的任務都定義在了主函數中,而自定義的線程,它的任務在哪兒呢?Thread類用於描述線程,線程是需要任務的,所以Thread類也有對任務的描述,這個任務就是通過Thread類中的run方法來體現,也就是說,run方法就是封裝自定義線程運行任務的函數,run方法中定義的就是線程要運行的任務代碼。開啓線程是爲了運行指定代碼,所以只有繼承Thread類,並覆寫run方法,將運行的代碼定義在run方法中即可。
多線程栗子:
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name ;
}
//複寫Thread類的run方法
public void run()
{
//Thread.currentThread().getName()當前線程名字
for(int x = 0;x < 10;x++)
{
System.out.println(name+"...x="+x+"....Threadname:"+Thread.currentThread().getName());
}
}
}
class ThreadDemo4
{
public static void main(String[] args)
{
Demo d1 = new Demo("毛1");
Demo d2 = new Demo("毛2");
//開啓線程,調用run方法
d1.start();
d2.start();
for(int x=0;x<10;x++)
{
System.out.println("x:"+x+"...over....."+"Threadname:"+Thread.currentThread().getName());
}
}
}
慄:
//準備擴展Demo類的功能,讓其中的內容可以作爲線程的任務執行
//通過接口的形式完成
class Demo implements Runnable
{
//複寫run方法
public void run()
{
show();
}
public void show()
{
for(int x=0;x<10;x++)
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
class ThreadDemo1
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
運行結果:
——4.線程安全問題
慄:
/*
需求:模擬4個線程賣100張票。
*/
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
public void run()
{
while (true)
{
//同步鎖,當有線程進入時,將會鎖住,在當前線程爲運行完前不允許進入
synchronized(obj)
{
if (num > 0)
{
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
}
}
}
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();
}
}
運行結果:
原因分析:從運行結果可以得知,安全問題已經解決,原因在於Object對象相當於是一把鎖,只有搶到鎖的線程,才能進入同步代碼塊向下執行。當num=1時,Cpu切換到某個線程後,其他線程將無法通過同步代碼塊繼而進行if判斷語句,只有等待前一個線程執行完num--操作,並且跳出同步代碼塊後,才能搶到鎖,其他線程即使搶到鎖,然而,此時num已經是0,也就無法通過if語句判斷,從而無法再執行num--操作,也就不會出現0、-1、-2的情況了。
——5.同步函數和同步代碼塊的區別
1.同步代碼塊的鎖是任意的對象
2.同步函數的鎖是固定的this。
建議使用同步代碼塊
同步函數的鎖是固定的this,同步代碼塊的鎖是任務的對象,那麼如果同步函數和同步代碼塊都使用this作爲鎖,就可以實現同步。
靜態的同步函數使用的鎖是該函數鎖屬字節碼文件對象,可以用getClass方法獲取,也可以用當前類名.class表示。
慄:
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
//使用this作爲鎖,而線程sleep需要拋出異常所以使用try/catch捕捉
synchronized(this.getClass())//Ticket.Class()
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...."+num--);
}
}
}
}
else
while(true)
show();
}
//當flag爲false時執行同步函數
public synchronized void show()
{
if(num > 0)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...."+num--);
}
}
}
class SynFunctionLockDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
/*
需要sleep10毫秒的原因是因爲可能線程t1尚未真正啓動,flag就被設置爲false,那麼當ti執行
時,就會按照flag爲false的情況執行,線程t2也按照flag爲false的情況執行.
*/
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
t.flag = false;
t2.start();
}
}
運行結果:
——6.多線程下的單例模式
餓漢式不存在安全問題,因爲不存在多個線程共同操作數據的情況
懶漢式存在安全問題,能夠使用同步函數解決:
class Single
{
public static Single s =null;
public void Single(){}
public static Single getInstance()
{
if(s == null)
{
Synchronized(this.getClass())//Single.class
{
if(s == null)
s = new Single();
}
}
return s;
}
}
如果使用同步函數,效率較低,因爲每次都需要判斷,而上部代碼的方式不會有這樣的問題,當任何一個線程執行到第一個if判斷語句時,Single對象已經創建,則直接能夠獲取,不用判斷是否能夠獲取鎖,相對於使用同步函數的方法就提升了效率。
——7.死鎖
當同步中嵌套同步時就有可能出現死鎖的現象。
慄:
/*
寫一個死鎖程序
*/
//定義一個類來實現Runnable,並複寫run方法
class LockTest implements Runnable
{
private boolean flag;
LockTest(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(LockClass.locka)//a鎖
{
System.out.println(Thread.currentThread().getName()+"------if_locka");
synchronized(LockClass.lockb)//b鎖
{
System.out.println(Thread.currentThread().getName()+"------if_lockb");
}
}
}
}
else
{
while(true)
{
synchronized(LockClass.lockb)//b鎖
{
System.out.println(Thread.currentThread().getName()+"------else_lockb");
synchronized(LockClass.locka)//a鎖
{
System.out.println(Thread.currentThread().getName()+"------else_locka");
}
}
}
}
}
}
//定義兩個鎖
class LockClass
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLock
{
public static void main(String[] args)
{
//創建2個進程,並啓動
new Thread(new LockTest(true)).start();
new Thread(new LockTest(false)).start();
}
}
運行結果:
——8.線程中通信
等待/喚醒機制設計的方法
1.這些方法都必須定義在同步中,因爲這些方法是用於操作線程狀態的方法。
2.必須要明確到底操作的是哪個鎖上的線程!
wait和sleep區別?
1)wait可以指定時間也可以不指定。sleep必須指定時間。
2)在同步中時,對CPU的執行權和鎖的處理不同。
wait:釋放執行權,釋放鎖。
sleep:釋放執行權,不釋放鎖。
爲什麼操作線程的方法wait、notify、notifyAll定義在了object類中,因爲這些方法是監視器的方法,監視器其實就是鎖。鎖可以是任意的對象,任意的對象調用的方式一定在object類中。
慄:
/*
有一個資源
一個線程往裏存東西,如果裏邊沒有的話
一個線程往裏取東西,如果裏面有得話
*/
//資源
class Resource
{
private String name;
private String sex;
private boolean flag=false;
public synchronized void setInput(String name,String sex)
{
if(flag)
{
try{wait();}catch(Exception e){}//如果有資源時,等待資源取出
}
this.name=name;
this.sex=sex;
flag=true;//表示有資源
notify();//喚醒等待
}
public synchronized void getOutput()
{
if(!flag)
{
try{wait();}catch(Exception e){}//如果木有資源,等待存入資源
}
System.out.println("name="+name+"---sex="+sex);//這裏用打印表示取出
flag=false;//資源已取出
notify();//喚醒等待
}
}
//存線程
class Input implements Runnable
{
private Resource r;
Input(Resource r)
{
this.r=r;
}
public void run()//複寫run方法
{
int x=0;
while(true)
{
if(x==0)//交替打印張三和王羲之
{
r.setInput("謝",".....man");
}
else
{
r.setInput("毛","..woman");
}
x=(x+1)%2;//控制交替打印
}
}
}
//取線程
class Output implements Runnable
{
private Resource r;
Output(Resource r)
{
this.r=r;
}
public void run()//複寫run方法
{
while(true)
{
r.getOutput();
}
}
}
<strong>
</strong>
class ResourceDemo6
{
public static void main(String[] args)
{
Resource r = new Resource();//表示操作的是同一個資源
new Thread(new Input(r)).start();//開啓存線程
new Thread(new Output(r)).start();//開啓取線程
}
}<strong>
</strong>
1)wait(),notify(),notifyAll(),用來操作線程爲什麼定義在了Object類中?
a,這些方法存在與同步中。
b,使用這些方法時必須要標識所屬的同步的鎖。同一個鎖上wait的線程,只可以被同一個鎖上的notify喚醒。
c,鎖可以是任意對象,所以任意對象調用的方法一定定義Object類中。
2)wait(),sleep()有什麼區別?
wait():釋放cpu執行權,釋放鎖。
sleep():釋放cpu執行權,不釋放鎖。
3)爲甚麼要定義notifyAll?
因爲在需要喚醒對方線程時。如果只用notify,容易出現只喚醒本方線程的情況。導致程序中的所以線程都等待。
/*
生產者生產商品,供消費者使用
有兩個或者多個生產者,生產一次就等待消費一次
有兩個或者多個消費者,等待生產者生產一次就消費掉
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
//多態
private Lock lock=new ReentrantLock();
//創建兩Condition對象,分別來控制等待或喚醒本方和對方線程
Condition condition_pro=lock.newCondition();
Condition condition_con=lock.newCondition();
//p1、p2共享此方法
public void setProducer(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();//解鎖,這個動作一定執行
}
}
//c1、c2共享此方法
public void getConsumer()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;
}
//複寫run方法
public void run()
{
while(true)
{
try
{
res.setProducer("商品");
}
catch (InterruptedException e)
{
}
}
}
}
//消費者線程
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res=res;
}
//複寫run
public void run()
{
while(true)
{
try
{
res.getConsumer();
}
catch (InterruptedException e)
{
}
}
}
}
class ProducerConsumer
{
public static void main(String[] args)
{
Resource res=new Resource();
new Thread(new Producer(res)).start();//第一個生產線程 p1
new Thread(new Consumer(res)).start();//第一個消費線程 c1
new Thread(new Producer(res)).start();//第二個生產線程 p2
new Thread(new Consumer(res)).start();//第二個消費線程 c2
}
}
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
class StopThread implements Runnable
{
private boolean flag =true;
public void run()
{
while(flag)
{
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.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
t1.interrupt();//清除凍結狀態
t2.interrupt();
st.changeFlag();//改變循環標記
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
運行結果:
當A線程執行到了b線程的.join()方法時,A線程就會等待,等B線程都執行完,A線程纔會執行。(此時B和其他線程交替運行。)join可以用來臨時加入線程執行。
2、setPriority()方法用來設置優先級
MAX_PRIORITY 最高優先級10
MIN_PRIORITY 最低優先級1
NORM_PRIORITY 分配給線程的默認優先級
3、yield()方法可以暫停當前線程,讓其他線程執行。