線程安全
當多個線程同時操作一個數據結構的時候,可能會發生一些奇妙的情況(比如相互串行或者相互修改),這種情況發生後就無法保證數據的一致性,這也是不安全的線程,爲了保證數據的一致性,我們在這裏討論線程安全。
1.首先我們新建一個線程
package ThreadSecure;
public class Thread_A extends Thread{
private work work; //這是工作
public Thread_A(work work)
{
this.work = work;
}
@Override
public void run()
{
work.add();
}
}
這是一個不包含任何線程安全寫法的線程,這在單例模式中是非常危險的。
這個線程主要實現一個功能就是運行一個“任務”,然後在任務結束後使得num獲得++,
2.然後我們編輯工作內容
package ThreadSecure;
public class work {
public int number = 0;
public void add()
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
number = number + 1 ;
System.out.println(Thread.currentThread().getName() + "-" + number);
}
}
3.然後我們編輯主函數,使得主函數創建三個線程,用來執行work.add()這個功能,然後觀察結果
package ThreadSecure;
public class ThreadMain {
public static void main(String[] args)
{
work work = new work();
for(int i = 0 ; i < 5 ; i++)
{
Thread_A p1 = new Thread_A(work);
p1.start();
}
try {
Thread.sleep(1001);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(work.number);
}
}
4.輸出結果
//第一次
Thread-0-1
Thread-4-2
Thread-1-3
Thread-3-3
Thread-2-2
3
//第二次
Thread-4-3
Thread-0-3
Thread-3-3
Thread-2-3
Thread-1-3
3
可以看到運行幾次的結果都是不同的,能不能按照我們所想,用number累計做了多少次work.add()完全是憑藉運氣!
所以在這裏使用JAVA的先進功能隱式鎖修改代碼
//關鍵字
synchronized(object);
隱式鎖
synchronzied是JAVA的一個關鍵字,在用其進行修飾代碼塊或者方法的時候,可以保證同一時刻最多隻有一個線程執行該代碼段,從此訪問需要講究順序,憑一個先來後到。
示例1:修飾於方法上,放在public等之後、返回類型之前
public synchronized void synMethod()
{
//method
}
示例2:修飾於代碼塊上
public int synMethod(int al){
synchronized (this){//synchronized(Object)
}//一次只有一個線程可以進入
//這裏面的object可以爲一個對象、參數本身、this,不建議對非對象使用
}
規則
- 當兩個進程併發地訪問一個對象的隱式鎖同步代碼塊時,同一個時間內只能有一個代碼塊得到執行
- 當一個線程訪問object的一個隱式鎖同步代碼塊時,另外一個線程可以訪問該對象中的非鎖條件(所以不建議對非對象使用,因爲容易混淆)
- 尤其關鍵的是當一個線程訪問object中的synchronized(this)方法塊時,其他線程對object中的所有其他synchronized(this)同步代碼塊的訪問將會被阻塞
- 也就是說,當一個線程訪問被隱式鎖鎖住的對象中的某個被鎖的代碼塊,其他所有線程對這個對象中的被鎖代碼的訪問都會被堵塞
- 以上規則適用於其他對象鎖
1.利用隱式鎖修改代碼
瞭解了隱式鎖後我們就嘗試用隱式鎖修改代碼,以完成我們之前的目的
package ThreadSecure;
public class work {
public int number = 0;
public synchronized void add()
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
number = number + 1 ;
System.out.println(Thread.currentThread().getName() + "-" + number);
}
}
可見我們在add方法上加入了一個隱式鎖
2.運行結果
Thread-0-1
Thread-1-2
2
Thread-4-3
Thread-3-4
Thread-2-5
程序走到add處得以一步一步地執行
三種隱式鎖的寫法
1.鎖方法體
class A{
public synchronized void method()
{
//method
}
}
2.鎖this
class A{
publicvoid method()
{
synchronized(this){
//method
}
}
}
3.鎖小東西
class A{
private byte [] lock = new byte[1];
public void method()
{
synchronized(lock){
//method
}
}
}
三種方法的速度大約是3>2>1
因爲鎖和解鎖一個對象是需要此對象的資源,所以鎖的對象越小,那麼效率也越高,與此同時,一個線程進入對象時調用資源也是需要一定時間的,在最關鍵的時候鎖當然比把線程“關”在門外面速度更快一點。造一個一字節的byte對象最小,所以在工作中經常這樣寫。