——- android培訓、java培訓、期待與您交流! ———-
我們知道,多線程帶來了方便,但同時也出現數據安全問題。
看如下買票的例子:
class Test implements Runnable
{
private int ticket = 500;
public void run()
{
while(true)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("ticket......."+ticket--);
}
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Test t = new Test();
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();
}
}
運行結果如下:
最後票數出現了0,甚至-1。當多線程操作同一共享資源時,由於線程執行延遲,可能會出現數據錯亂問題。對於這種問題我們該如何解決?
線 程 同 步
線程同步的作用:解決多線程數據安全問題
將涉及到共享數據的多行代碼封裝到同步代碼中,相當於對這部分代碼上鎖,在某一時刻只有一個線程對其進行操作,避免了數據錯亂問題。
線程同步的方法兩種
第一種:同步代碼塊
第二種:同步函數
(一)同步代碼塊
格式爲: synchronized(對象){ 需要同步的代碼 },
說明:對象,一般爲Object obj = new Object();直接傳入到synchronized(obj)
更改以上代碼,使用同步代碼塊
class Test implements Runnable
{
private int ticket = 500;
Object obj = new Object();
public void run()
{
while(true)
{ synchronized(obj)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("ticket......."+ticket--);
}
}
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Test t = new Test();
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();
}
}
程序運行結果如下:
此時沒有出現數據錯誤,解決了上述問題。
synchronized(obj)參數可以是任意一個Object類或者他的子類。
註解:object相當於一個同步鎖,進入之前需要判斷,當一個線程在執行同步代碼塊時,同步鎖是關閉的,即時其他線程獲得了執行權也不能進入,而當一個線程執行完跳出同步代碼塊時,同步鎖是開啓的,其他獲得執行權的線程纔可以操作,故在同一時間點,只有一個線程在執行同步代碼塊的所有代碼,保證了共享數據的安全性。
判斷哪些代碼塊需要同步遵循以下三個步驟:
1)明確哪些代碼是多線程運行代碼;
2)明確共享數據;
3)明確多線程運行代碼中哪些是操作共享數據的。
看一個例子: 銀行一個金庫,兩個人一人存300元,每個人一次存100元,分3次存完。
class Bank implements Runnable
{
private int money = 0;
Object obj = new Object();
public void run()
{
synchronized(obj)
{ for(int i=0;i<3;i++)
{
money = money +100;
System.out.println("money......."+money);
}
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Bank t = new Bank();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
運行結果如下:
判斷同步的兩個關鍵點:
第一點:是否有兩個及兩個以上的線程;
第二點:這些線程是否使用同一個同步鎖。
(二)使用同步函數
1)直接在函數上加入關鍵字synchronized,同步鎖爲this。
2)靜態同步函數的同步鎖爲該方法所在類的字節碼文件對象,即類名.class
舉例說明同步函數的同步鎖爲this。
用兩個線程來買票,一個處於同步代碼塊中,一個處於同步函數中,都在執行買票動作。
class Ticket implements Runnable
{
private int ticket = 100;
Object obj = new Object();
static boolean flag = true;
public void run()
{
if(flag)
{ while(true)
{
synchronized(obj)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+ticket--);
}
}
}
}else
{
while(true)
{
show();
}
}
}
private synchronized void show(){
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show..."+ticket--);
}
}
}
public class ThreadDemo
{
public static void main(String args[])
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{t1.sleep(10);}catch(Exception e){}
Ticket.flag = false;
t2.start();
}
}
此時運行結果如下:
出現了數據異常,說明沒有同步,說明同步代碼塊和同步函數不是同一個鎖,而此時同步代碼塊的鎖爲obj,如果把這個鎖改爲this,看下結果如何?
class Ticket implements Runnable
{
private int ticket = 100;
static boolean flag = true;
public void run()
{
if(flag)
{ while(true)
{
synchronized(this)
{
if(ticket>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+ticket--);
}
}
}
}else
{
while(true)
{
show();
}
}
}
結果如下:
此時無論運行多少次,結果都不會出現0,數據異常現象了,反面說明了同步函數的同步鎖爲this。
同樣方法,可以驗證靜態同步函數的同步鎖爲類名.class。
到此爲止,把多個線程操控同一共享資源的數據安全問題利用同步代碼塊或同步函數得到了解決,有什麼問題和意見歡迎提出。
下集講述多個線程對同一資源具有不同操作行爲時需要解決的問題—-等待喚醒機制。