併發安全的特性 – 原子性:鎖
JDK爲我們提供了兩種我們比較常見的鎖,分別是:
第一種,就是我們比較熟悉的synchronized: 依賴於JVM的關鍵字。
第二種,是Lock: 依賴特殊的CPU指令,代碼實現,ReektrantLock。
原子性:synchronized
主要有以下幾種使用方法:
一、修飾代碼塊
大括號括起來的代碼,作用於調用的對象。指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。 和 synchronized 方法一樣,synchronized(this)代碼塊也是鎖定當前對象的。synchronized 關鍵字加到 static 靜態方法和synchronized(class)代碼塊上都是是給 Class 類上鎖。這裏再提一下:synchronized關鍵字加到非 static 靜態方法上是給對象實例上鎖。另外需要注意的是:儘量不要使用 synchronized(String a) 因爲JVM中,字符串常量池具有緩衝功能!
二、修飾實例方法
子類繼承該方法時,不能繼承synchronized關鍵字,作用在同一個對象。
private static Object obj = new Object();
//修飾一個代碼塊(作用在同一個對象)
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 100; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
//修飾一個方法(子類繼承該方法時,不能繼承synchronized關鍵字)
//作用在同一個對象
public synchronized void test2(int j) {
for (int i = 0; i < 100; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
System.out.println(executorService.submit(() -> {
example1.test1(1);
}).get());
executorService.submit(() -> {
example1.test1(2);
});
executorService.shutdown();
}
這個例子的結果是,當同一個對象調用被synchronized修飾的代碼塊或者修飾的實例方法,線程1執行完畢之後,線程2纔可以繼續執行。讀者可以自行更改調用方法或者調用方法的對象,觀察運行結果會有什麼不同。
三、修飾一個類(作用在使用該方法的所有對象)
四、修飾一個靜態方法(作用在使用該方法的所有對象)
子類繼承該方法時,不能繼承synchronized關鍵字,作用於當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖 也就是給當前類加鎖,會作
用於類的所有對象實例,因爲靜態成員不屬於任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份,所以對該類的所有對象都加了鎖)。所以如果一個線程A調用一個實例對象的非靜態 synchronized 方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因爲訪問靜態 synchronized 方法佔用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖。
//修飾一個類(作用在使用該方法的所有對象)
public static void test1(int j) {
synchronized (SynchronizedExample2.class) {
for (int i = 0; i < 1000; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
//修飾一個靜態方法(子類繼承該方法時,不能繼承synchronized關鍵字)
//作用在使用該方法的所有對象
public synchronized static void test2(int j) {
for (int i = 0; i < 1000; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test1(1);
});
executorService.execute(() -> {
example2.test1(2);
});
executorService.shutdown();
}
同樣的這個例子的結果是,當不同對象調用被synchronized修飾的類的方法或者修飾的靜態方法,依然是線程1執行完畢之後,線程2纔可以繼續執行,這就說明這兩種方式是進入同步代碼前要獲得當前類對象的鎖。
關於synchronized和Lock的對比
synchronized: 是不可能中斷的,適合競爭不是特別激烈的情形,可讀性較好。
Lock: 可中斷鎖,多樣化同步,競爭激烈時能維持常態。