-
線程安全
- 當多個線程訪問訪問某一個
類(對象或方法)
時,這個類或對象或方法始終
能表現出正確的行爲
或我們想要的結果
,那麼這個類(對象或方法)就是線程安全
的。- synchronized:可以在
任意的對象
及方法
上加鎖,而加鎖的這段代碼稱之爲互斥區
或者臨界區
。
代碼示例說明1
1:不加synchronized關鍵字修飾run()方法的代碼
public class MyThread extends Thread {
private int count = 5;
public void run() {
count--;
System.out.println(this.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
/**
* 分析:當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裏排對是按照CPU分配的先後順序而定的), 一個線程想要執行synchronized修飾的方法裏的代碼: 1 嘗試獲得鎖 2
* 如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止, 而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
*/
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread, "t1");
Thread t2 = new Thread(myThread, "t2");
Thread t3 = new Thread(myThread, "t3");
Thread t4 = new Thread(myThread, "t4");
Thread t5 = new Thread(myThread, "t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
運行結果如下,沒有出現我們想要的結果,打印出來的線程名是無序的 count值也沒按正常–,運行多次不能保證count打印的值每次一致,因此出現了線程安全問題。
t1 count = 2
t2 count = 2
t5 count = 0
t3 count = 2
t4 count = 1
代碼示例說明2
1:當我們加上synchronized關鍵字修飾run()方法後,代碼如下。
public class MyThread extends Thread {
private int count = 5;
public synchronized void run() {
count--;
System.out.println(this.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
/**
* 分析:當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裏排對是按照CPU分配的先後順序而定的), 一個線程想要執行synchronized修飾的方法裏的代碼: 1 嘗試獲得鎖 2
* 如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止, 而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)
*/
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread, "t1");
Thread t2 = new Thread(myThread, "t2");
Thread t3 = new Thread(myThread, "t3");
Thread t4 = new Thread(myThread, "t4");
Thread t5 = new Thread(myThread, "t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
加上synchronized
運行結果如下,線程名無序,無論你執行多少次程序,count–的值都是顯示我們想要的正確結果。
t1 count = 4
t3 count = 3
t4 count = 2
t5 count = 1
t2 count = 0
線程安全小結:
當多個線程訪問Mythread的run方法時,以排隊的方式進行處理(排隊的方式是按照CPU分配的餓先後順序而定的),一個線程想要執行synchronized修飾的方法裏的代碼,首先嚐試獲得鎖,如果拿到鎖,執行synchronized中代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到爲止,而且是多個線程同時去競爭這把鎖,也就是會有競爭鎖的問題。
-
多個線程多個鎖
多個線程多個鎖:多個線程,每個線程都可以拿到自己指定的鎖,分別獲得鎖之後,執行synchronized方法體的內容。
代碼示例說明1
1:兩個線程t1,t2分別依次start,訪問兩個對象的synchronized修飾的printNum方法,Code如下:
/**
* 關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,
* 所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock),
*
* 在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
* @author xujin
*
*/
public class MultiThread {
private int num = 0;
public synchronized void printNum(String tag) {
try {
if (tag.equals("a")) {
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 注意觀察run方法輸出順序
public static void main(String[] args) {
// 兩個不同的對象
final MultiThread m1 = new MultiThread();
final MultiThread m2 = new MultiThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
執行結果如下:
tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100
關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖(Lock)
代碼示例說明2
1:在靜態方法上printNum()加一個synchronized關鍵字修飾的話,那這個線程調用printNum()獲得鎖,就是這個類級別的鎖。這是時候無論你實例化出多少個對象m1,m2都是沒有任何關係的,代碼Demo如下所示:
public class MultiThread {
// ②修改爲static關鍵字修飾
private static int num = 0;
// ①修改爲static修飾該方法
public static synchronized void printNum(String tag) {
try {
if (tag.equals("a")) {
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 注意觀察run方法輸出順序
public static void main(String[] args) {
// 倆個不同的對象
final MultiThread m1 = new MultiThread();
final MultiThread m2 = new MultiThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
運行結果如下,可以看出t1執行完了
,然後執行t2
,他們之間有一個順序:
tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200
多個線程多個鎖小結:
- 關鍵字
synchronized
取得的鎖
都是對象鎖
,而不是把一段代碼或方法當做鎖
,所以示例中代碼中的哪個線程先執行synchronized關鍵字的方法
,哪個線程就持有該方法對象的鎖
,也就是Lock,兩個對象,線程獲得的就是兩個不同的鎖,他們互不影響。- 有一種情況則是相同的鎖,即在靜態方法上加
synchronized
關鍵字,表示鎖定.class類
,類一級別的鎖獨佔.class類。
-
對象鎖的同步和異步
鎖同步和異步的概念
- 同步-synchronized同步的概念就是共享,需要記住共享這個概念,如果不是共享的資源,就沒有必要同步。
- 異步-asynchronized異步是相互獨立的,相互之間不受任何約制,類似於http中的Ajax請求。
- 同步的目的就是爲了線程安全,其實對於線程安全來說,需要滿足兩個特性:
原子性
,可見性
代碼示例1
public class TestObject {
/** synchronized */
public synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName());
//休眠4秒
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method2() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
final TestObject mo = new TestObject();
/**
* 分析: t1線程先持有TestObject對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
* t1線程先持有TestObject對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步
*/
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mo.method1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
mo.method2();
}
}, "t2");
t1.start();
t2.start();
}
}
運行結果如下,因爲t1,t2兩個線程訪問TestObject對象的mo的method1,method2方法是異步的,所以直接打出:
t2
t1
分析: t1線程若先持有TestObject對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法,這就是異步。
代碼示例2
把上面代碼中的method2,也加上synchronized
去修飾,代碼如下:
public class TestObject {
public synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/** 加上synchronized修飾method2 */
public synchronized void method2() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
final TestObject mo = new TestObject();
/**
* 分析: t1線程先持有TestObject對象的Lock鎖,t2線程可以以異步的方式調用對象中的非synchronized修飾的方法
* t1線程先持有TestObject對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步
*/
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mo.method1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
mo.method2();
}
}, "t2");
t1.start();
t2.start();
}
}
打印結果如下,由於CPU隨機分配,若t1線程先執行,先打印t1,然後t1線程先休眠4s,後釋放了Lock,然後打印t2。
t1
t2
t1線程先持有TestObject對象的Lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步