線程的狀態(tài)

線程的狀態(tài)
線程安全問題
-
案例:售票的例子。
class Ticket implements Runnable
{
//1,描述票的數(shù)量。
private int tickets = 100;
//2,售票的動(dòng)作,這個(gè)動(dòng)作需要被多線程執(zhí)行,那就是線程任務(wù)代碼。需要定義run方法中。
//線程任務(wù)中通常都有循環(huán)結(jié)構(gòu)。
private Object obj = new Object();
public void run()
{
while(true)
{
if(tickets>0)
{
//要讓線程在這里稍停,模擬問題的發(fā)生。sleep 看到了0 -1 -2 錯(cuò)誤的數(shù)據(jù),這就是傳說中的多線程安全問題。
try{Thread.sleep(1);}catch(InterruptedException e){}
//打印線程名稱。
System.out.println(Thread.currentThread().getName()+"....."+tickets--);
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
//1,創(chuàng)建Runnable接口的子類對(duì)象。
Ticket t = new Ticket();
//2,創(chuàng)建四個(gè)線程對(duì)象。并將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//3,開啟四個(gè)線程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}
售票的動(dòng)作需要同時(shí)執(zhí)行,所以使用多線程技術(shù)。
發(fā)生了線程安全問題:出現(xiàn)了錯(cuò)誤的數(shù)據(jù)。0 -1 -2
問題產(chǎn)生的原因;
1,線程任務(wù)中在操作共享的數(shù)據(jù)。
2,線程任務(wù)操作共享數(shù)據(jù)的代碼有多條(運(yùn)算有多個(gè))。
解決思路:
只要讓一個(gè)線程在執(zhí)行線程任務(wù)時(shí)將多條操作共享數(shù)據(jù)的代碼執(zhí)行完,
在執(zhí)行過程中,不要讓其他線程參與運(yùn)算。就哦了。
代碼體現(xiàn)呢?
Java中解決此問題通過代碼塊來完成的。
這個(gè)代碼塊:同步代碼塊 synchronized
格式:
synchronized(對(duì)象)
{
//需要被同步的代碼。
}
同步好處:
解決多線程安全問題。
同步弊端:
降低了程序的性能。
同步前提:
必須保證多個(gè)線程在同步中使用的是同一個(gè)鎖。
解決了什么問題?
當(dāng)多線程安全問題發(fā)生時(shí),加入了同步后,
問題依舊,就要通過這個(gè)同步的前提來判斷同步是否寫正確。
-
使用同步代碼塊
class Ticket implements Runnable
{
//1,描述票的數(shù)量。
private int tickets = 100;
//2,售票的動(dòng)作,這個(gè)動(dòng)作需要被多線程執(zhí)行,那就是線程任務(wù)代碼。需要定義run方法中。
//線程任務(wù)中通常都有循環(huán)結(jié)構(gòu)。
private Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tickets>0)
{
//要讓線程在這里稍停,模擬問題的發(fā)生。sleep 看到了0 -1 -2 錯(cuò)誤的數(shù)據(jù),這就是傳說中的多線程安全問題。
try{Thread.sleep(1);}catch(InterruptedException e){/*未寫處理方式,后面講*/}
System.out.println(Thread.currentThread().getName()+"....."+tickets--);//打印線程名稱。
}
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
//1,創(chuàng)建Runnable接口的子類對(duì)象。
Ticket t = new Ticket();
//2,創(chuàng)建四個(gè)線程對(duì)象。并將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//3,開啟四個(gè)線程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}
-
使用同步函數(shù)
同步的另一種體現(xiàn)形式:同步函數(shù)。
同步函數(shù)使用的鎖是哪個(gè)?
經(jīng)過分析:大概猜的是this,因?yàn)楹瘮?shù)必須被對(duì)象調(diào)用。
驗(yàn)證:
寫一個(gè)同步代碼塊,寫一個(gè)同步函數(shù),如果同步代碼塊中的鎖對(duì)象和同步函數(shù)中的鎖對(duì)象是同一個(gè),
就同步了,就沒有錯(cuò)誤的數(shù)據(jù)了。如果不是同一個(gè)鎖對(duì)象,就不同步出現(xiàn)錯(cuò)誤數(shù)據(jù)。
讓兩個(gè)線程,一個(gè)線程在同步代碼塊中執(zhí)行,一個(gè)線程在同步函數(shù)中執(zhí)行。
- 非靜態(tài)同步函數(shù)使用的鎖是this
class Ticket implements Runnable
{
private int tickets = 100;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true){
//驗(yàn)證同步函數(shù)使用的鎖對(duì)象是this
synchronized(this){
if(tickets>0){
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印線程名稱。
}
}
}
}
else{
while(true){
this.sale();
}
}
}
public synchronized void sale()//同步函數(shù),使用的鎖對(duì)象 this。
{
if(tickets>0)
{
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印線程名稱。
}
}
}
class ThreadDemo4
{
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(InterruptedException e){}
//切換標(biāo)記,之前,讓主線程停一會(huì),這時(shí)就只有一個(gè)t1線程在,它就會(huì)執(zhí)行同步代碼塊。
t.flag = false;
t2.start();
}
}
- 靜態(tài)同步函數(shù)使用的鎖是字節(jié)碼文件對(duì)象, 類名.class
class Ticket implements Runnable
{
private static int tickets = 100;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true){
synchronized(Ticket.class){
if(tickets>0){
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印線程名稱。
}
}
}
}
else{
while(true){
this.sale();
}
}
}
public static synchronized void sale()//
{
if(tickets>0)
{
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印線程名稱。
}
}
}
class ThreadDemo5
{
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(InterruptedException e){}
//切換標(biāo)記,之前,讓主線程停一會(huì),這時(shí)就只有一個(gè)t1線程在,它就會(huì)執(zhí)行同步代碼塊。
t.flag = false;
t2.start();
}
}
- 總結(jié)
總結(jié):非靜態(tài)同步函數(shù)使用的鎖是this。
靜態(tài)同步函數(shù)使用的鎖是字節(jié)碼文件對(duì)象, 類名.class
同步函數(shù)和同步代碼塊有什么區(qū)別嗎?
同步函數(shù)使用的鎖是固定的this或 類名.class。當(dāng)線程任務(wù)只需要一個(gè)同步時(shí)完全可以使用同步函數(shù)。
同步代碼塊使用的鎖可以是任意對(duì)象。當(dāng)線程任務(wù)中需要多個(gè)同步時(shí),必須通過鎖來區(qū)分,這時(shí)必須使用同步代碼塊。
同步代碼塊較為常用。
synchronized的一點(diǎn)思考
在run方法上加上synchronized后變成同步函數(shù),就是讓整個(gè)程序變成單線程,降低了程序的效率,所以只在操作共享數(shù)據(jù)的代碼加上synchronized就可以了。
class Ticket implements Runnable
{
//1,描述票的數(shù)量。
private int tickets = 100;
//2,售票的動(dòng)作,這個(gè)動(dòng)作需要被多線程執(zhí)行,那就是線程任務(wù)代碼。需要定義run方法中。
//線程任務(wù)中通常都有循環(huán)結(jié)構(gòu)。
private Object obj = new Object();
public synchronized void run()
{
while(true)
{
if(tickets>0)
{
//要讓線程在這里稍停,模擬問題的發(fā)生。sleep 看到了0 -1 -2 錯(cuò)誤的數(shù)據(jù),這就是傳說中的多線程安全問題。
try{Thread.sleep(1);}catch(InterruptedException e){}
//打印線程名稱。
System.out.println(Thread.currentThread().getName()+"....."+tickets--);
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
//1,創(chuàng)建Runnable接口的子類對(duì)象。
Ticket t = new Ticket();
//2,創(chuàng)建四個(gè)線程對(duì)象。并將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//3,開啟四個(gè)線程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}