多執行緒 繼承Thread類 實現Runnable介面 多執行緒安全 synchronized 單例設計懶漢 鎖死
程序
是一個正在執行中的程式。
每個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
舉例子:一個程序,就是一個正在執行的程式。
執行緒
就是程序中的一個獨立的控制單元。
執行緒在控制著程序的執行。
舉例子:例如生產流水線,一個廠房如果只有一條流水線,只能是產品生產完一個再生產一個。如果多裝幾條流水線,就可以同時生產多個,這樣提高了效率,每一條流水線,就可以理解為一個執行緒。
一個程式中至少有一個執行緒。
java vm 啟動的時候會有一個程序java.exe。
該程序中至少有一個執行緒負責java程式的執行。
而且這個執行緒執行的程式碼存在於main方法中。
該執行緒稱之為主執行緒。
擴充套件:其實更細節說明jvm,jvm啟動不止一個執行緒,還有負責垃圾回收機制的執行緒。
Thread類
建立執行緒的第一種方式,:繼承Thread類。
步驟:
1,定義類繼承Thread。
2,複寫Thread類中的run方法。
目的:將自定義的程式碼儲存在run方法中,讓執行緒執行
3,呼叫執行緒的start方法
該方法有兩個作用,啟動執行緒,呼叫run方法。
發現執行結果每次都不一樣。class Demo extends Thread { public void run() { for (int x=0;x<60 ;x++ ) System.out.println("demo run"+x); } } class Test { public static void main(String[] args) { Demo d = new Demo(); //建立一個物件,就是建立一個執行緒 d.start(); //開啟執行緒並執行該執行緒的run方法。 //d.run; //僅僅是物件呼叫方法,而執行緒建立類,並沒有執行。 for (int x=0 ; x<60 ;x++ ) System.out.println("hahah------"+x); } }
因為多個執行緒都在獲取CPU的執行權,cpu執行到誰,誰就執行。
明確一點,在某一時刻,只能有一個程式在執行。(多核除外)
cpu在做著快速的切換,已達到看上去是同時執行的效果。
我們可以形象把多執行緒的執行行為在互相搶奪cpu的執行權。
這就是多執行緒的一個特性:隨機性,誰搶到誰執行,至於執行多長,由cpu說的算。
為什麼要覆蓋run方法呢?
Thread類用於描述執行緒。
該類就定義了一個功能,用於儲存執行緒要執行的程式碼,該儲存功能就是run方法。
也就是說Thread類中的run方法,用於儲存執行緒要執行的程式碼。
多執行緒 狀態/* 聯絡 建立兩個執行緒,和主執行緒交替執行。 */ class A extends Thread { private String name; A(String name) { this.name=name; } public void run() { for (int x=0;x<10 ;x++ ) { System.out.println(name+"+"+x); } } } class Test { public static void main(String[] args) { A a1 = new A("one"); A a2 = new A("two"); a1.start(); a2.start(); for (int x=0 ; x<10 ;x++ ) System.out.println("hahah------"+x); } }
臨時狀態(阻塞)
具備執行資格,但是沒有執行權
^
^ sleep(time)
| —————————————————>>>
| sleep(時間到)
start() | <<<—————————————————
被建立——————————>>> 執行 wait() 凍結
| —————————————————>>> 沒有執行資格
| notify()
stop(); | <<<—————————————————
run方法結束 |
V
V
消亡
執行緒都有自己預設的名稱
this.getName() 獲取執行緒名
Thread-編號 ,該編號從0開始。
class A extends Thread
{
public void run()
{
for (int x=0;x<10 ;x++ )
{
System.out.println(this.getName()+"-----"+x); //getName獲取執行緒名
}
}
}
class Test
{
public static void main(String[] args)
{
A a1 = new A();
A a2 = new A();
a1.start();
a2.start();
for (int x=0 ; x<10 ;x++ )
System.out.println("hahah------"+x);
}
}
自定義Thread名稱
class A extends Thread
{
A(String name)
{
super(name); // 傳給父類名稱
}
public void run()
{
for (int x=0;x<10 ;x++ )
{
System.out.println(this.getName()+"-----"+x); //獲取名稱
}
}
}
class Test
{
public static void main(String[] args)
{
A a1 = new A("ONE");
A a2 = new A("TWO");
a1.start();
a2.start();
for (int x=0 ; x<10 ;x++ )
System.out.println("hahah------"+x);
}
}
(static Thread) currentThead(); 獲取當前執行緒物件
getName(); 獲取執行緒名稱。
設定執行緒名成: setName 或者 建構函式(直接 super(name) )。
class A extends Thread
{
A(String name)
{
super(name);
}
public void run()
{
for (int x=0;x<10 ;x++ )
{
System.out.println((Thread.currentThread()==this)+"-----"+this.getName()+"----"+x);
}
}
}
class Test
{
public static void main(String[] args)
{
A a1 = new A("ONE");
A a2 = new A("TWO");
a1.start();
a2.start();
for (int x=0 ; x<10 ;x++ )
System.out.println("hahah------"+x);
}
}
Runnable介面
建立執行緒的第二種方法 : 實現Runnable介面
為什麼要有Runnable:因為在實際情況中,有可能出現一個類已經是子類,已經繼承了別的類,這時可能需要多執行緒的應用。由於java中單繼承的特性,所以設計出Runnable介面,用於解決繼承類父類的情況下,實現多執行緒的情況
步驟:
1,定義類實現Runnable介面。
2,覆蓋Runnable介面中的run方法。
將執行緒要執行的程式碼放在該run方法中。
3,通過Thread類建立執行緒物件。
4,將Runnalbe介面的子類物件作為實際引數傳遞給Thread類的建構函式.
為什麼要將Runnable介面的子類物件傳遞給Thread的建構函式。
因為,自定義的run方法所屬的物件是Runnable介面的子類物件。
所以要讓執行緒去執行指定物件的run方法。就必須明確該run方法的所屬物件。
5,呼叫Thread類的start方法開啟執行緒並呼叫Runnable介面子類中的run方法。
/*
需求:簡單的賣票程式。
多個視窗買票。
*/
class Ticket implements Runnable
{
private int tick = 100;
public void run()
{
while(true)
{
if(tick>0)
System.out.println(Thread.currentThread().getName()+"---sale:"+tick--);
}
}
}
class Test
{
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();
}
}
Runnable實現方式和Thread繼承方式的區別
繼承Thread:只能單繼承,執行緒程式碼存放在Thread子類的run方法中。
實現Runnable:可以避免單繼承的侷限性,執行緒程式碼存放在Runnable介面的子類的run方法中。
多執行緒的安全問題
問題原因:
當多條語句在操作同一個執行緒共享資料時,一個執行緒對多條語句執行了一部分,沒有執行完。
另一個執行緒參與進來執行,導致共享資料的錯誤。
解決辦法:
對多條操作共享資料的語句,只能讓一個執行緒執行完,在執行過程中,其他執行緒不可以參與執行。
java對於多執行緒的安全問題提供類專業的解決方式。
就是同步程式碼塊。
synchronized (物件)
{
需要被同步的程式碼;
}
物件如同鎖,持有鎖的執行緒可以在同步中執行。
沒有持有鎖的執行緒即使獲取cpu的執行權,也進不去,因為沒有獲取門鎖。
如果火車上的衛生間。
同步的前提
1,必須要有兩個或者兩個以上的執行緒。
2,必須是多個執行緒使用同一個鎖。
必須保證同步中只有一個執行緒在執行。
好處:解決了多執行緒的安全問題。
弊端:多個執行緒都需要判斷鎖,較為消耗資源。
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
}
}
}
}
}
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();
如何找安全問題:
1,明確哪些程式碼是多執行緒執行程式碼。
2,明確共享資料。
3,明確多執行緒執行程式碼中哪些語句是操作共享資料的。
class Bank
{
private int sum;
public synchronized void add(int n)
{
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 Test
{
public static void main (String[] args)
{
Cus s= new Cus();
Thread s1= new Thread(s);
Thread s2= new Thread(s);
s1.start();
s2.start();
}
}
同步程式碼塊
class Bank
{
private int sum;
Object obj = new Object();
public void add(int n)
{
synchronized()
{
sum+=n;
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
System.out.println("sum="+sum);
}
}
}
同步函式
class Bank
{
private int sum;
Object obj = new Object();
public void synchronized add(int n)
{
sum+=n;
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
System.out.println("sum="+sum);
}
}
同步函式用的是哪一個鎖?
函式需要被物件呼叫,那麼函式都有一個所屬物件引用,就是this。
所以同步函式使用的鎖是this。
驗證
一個執行緒在同步程式碼塊中,
一個執行緒在同步函式中。
都在執行買票動作。
class Ticket implements Runnable
{
private int tick = 100;
boolean flag = true;
public void run()
{
if (flag)
{
while(true)
{
synchronized (this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch (InterruptedException 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 (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....show...."+tick--);
}
}
}
class Test
{
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();
}
}
static修飾同步函式
如果同步函式被靜態修飾後,使用的鎖匙class物件。
驗證發現,不是this,因為靜態方法中不可以定義this。
靜態進記憶體時,記憶體中沒有本類物件,但是一定有該類對應的位元組碼檔案物件。
類名.class 。 該物件的型別是calss。
靜態的同步方法,使用的鎖是 該方法所在類的位元組碼檔案物件。 類名.class
class Ticket implements Runnable
{
private static int tick = 100;
boolean flag = true;
public void run()
{
if (flag)
{
while(true)
{
synchronized (Ticket.class) // 鎖為 類名.class
{
if(tick>0)
{
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....code...."+tick--);
}
}
}
}
else
while(true)
show();
}
public static synchronized void show() // static修飾 synchronized
{
if(tick>0)
{
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....show...."+tick--);
}
}
}
單例設計模式--懶漢式
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 SingleDemo
{
public static void main(String [] args)
{
Single s1=Single.getInstance();
Single s2=Single.getInstance();
System.out.println(s1==s2);
}
}
死鎖
同步中巢狀同步。
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if (flag)
{
while(true)
{
synchronized (MyLock.Locka)
{
System.out.println("1");
synchronized (MyLock.Lockb)
{
System.out.println("2");
}
}
}
}
else
{
while(true)
{
synchronized (MyLock.Lockb)
{
System.out.println("3");
synchronized (MyLock.Locka)
{
System.out.println("4");
}
}
}
}
}
}
class MyLock
{
static Object Locka = new Object();
static Object Lockb = new Object();
}
class DeadlockTect
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}