文章目錄
使用場景
public synchronized void test2(){
log.info("實例方法");
}
public synchronized static void test3(){
log.info("靜態方法");
}
public void test4(){
log.info("代碼塊");
}
引出兩個小問題:
一個類中同時有synchronized static方法和synchronized的方法,這兩個方法同步嗎?
不同步
- 靜態方法與成員方法的區別是,靜態方法歸屬類,成員方法歸屬於對象
- synchronized方法鎖定的是當前對象
- 如果是靜態同步方法,鎖定的是類的Class對象。注意這裏不是鎖定此類的所有對象,僅是唯一的Class對象。
- 如果是普通同步方法,鎖定的是調用該方法的那個對象。
下面是我之前測試的代碼,如果兩個方法是同步的話,結果應該是一個線程鎖定對象循環輸出完釋放對象後,另一個線程再循環輸出,但實際上是兩個線程交替執行的,所以這兩個方法不同步。
public class Test {
public static void main(String[] args) {
Test test=new Test();
test.new T1().start();
test.new T2().start();
}
public synchronized static void func1() {
for(int i=0;i<1000;i++) {
System.out.println("this is a synchronized static method");
}
}
public synchronized void func2() {
for(int i=0;i<1000;i++) {
System.out.println("this is a synchronized method");
}
}
class T1 extends Thread{
@Override
public void run() {
func1();
}
}
class T2 extends Thread{
@Override
public void run() {
func2();
}
}
}
當一個線程進入一個對象的synchronized方法後,其他線程是否可以進入此對象的其他方法?
- 其他方法如果是synchronized方法
- 如果當前synchronized方法中沒有調用對象的wait方法,則其他線程不可以進入
- 如果當前synchronized方法中調用了對象的wait方法,則其他線程可以進入
- 其他方法如果是普通的成員方法或者static方法,則其他線程可以進入,因爲未用synchronized修飾的方法意味着不需要數據同步。
- 其他方法如果是synchronized static方法,則其他線程可以進入,因爲因爲synchronized成員方法同步鎖定的是當前方法所屬實例對象,synchronized static方法同步鎖定的是當前類的Class對象,並非同一個對象。
特性
JVM中對象構成
對象頭
Mark Word
(標記字段):默認存儲對象的HashCode,分代年齡和鎖標誌位信息。它會根據對象的狀態複用自己的存儲空間,也就是說在運行期間Mark Word
裏存儲的數據會隨着鎖標誌位的變化而變化。Klass Point
(類型指針):對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
實例數據
存放類的數據信息,父類的信息。
對齊填充
由於虛擬機要求對象起始地址必須是8字節的整數倍,填充數據不是必須存在的,僅僅是爲了字節對齊。
JMM內存模型
https://blog.csdn.net/Chill_Lyn/article/details/106056359
可見性、原子性、有序性
可重入性
synchronized鎖對象的時候有個計數器,他會記錄下線程獲取鎖的次數,在執行完對應的代碼塊之後,計數器就會-1,直到計數器清零,就釋放鎖了。
不可中斷性
一個線程獲取鎖之後,另外一個線程處於阻塞或者等待狀態,前一個不釋放,後一個也一直會阻塞或者等待,不可以被中斷。
底層實現
測試代碼
public class SynchronizedTest {
//同步方法
public synchronized void test1(){
//同步代碼塊
synchronized (Object.class){
System.out.println(123);
}
}
}
找到編譯後的.class文件,執行javap -c -v -p SynchronizedTest.class
命令
ACC_SYNCHRONIZED
是同步方法的標誌位。
monitorenter
和monitorexit
是同步代碼塊的進出標誌位。
synchronized
底層的源碼就是引入了ObjectMonitor
重量級鎖
在jdk1.6之前synchronized被稱爲重量級鎖,是因爲底層實現的ObjectMonitor
的調用過程涉及用戶態和內存態的切換,這是Linux內核的複雜運行機制決定的,大量消耗系統資源,所以效率低
優化鎖升級
流程
- 當一個線程需要鎖資源時,首先判斷當前資源鎖持有者是否與當前線程是同一線程,如果是,拿到鎖。(偏向鎖/可重入)
- 如果不是同一線程,通過自旋+CAS嘗試獲取鎖(自旋鎖)
- 默認自旋10次,最終仍沒有獲得鎖,升級爲重量鎖
簡單說升級流程就是:無鎖->偏向鎖->輕量級鎖->重量鎖
synchronized和Lock的區別
- 原始構成
- synchronized關鍵字屬於JVM層面,底層是通過monitor對象來完成,wait/notify方法也依賴於monitor對象,只有在同步塊和方法中才能調用。
- Lock是具體類(java.util.concurrent.locks.Lock),是API層面的鎖
- 使用方法
- synchronized不需要用戶手動釋放鎖,當synchronized代碼塊執行完畢後系統會自動讓線程釋放對鎖的佔用
- ReentrantLock需要用戶手動釋放鎖,如果沒有主動釋放鎖,可能導致出現死鎖現象。需要lock() unlock()配合try-finally語句塊來完成
- 是否可中斷
- synchronized不可中斷,除非拋出異常或者正常運行完成
- ReentrantLock可中斷:
- 設置超時方法tryLock(Long timeout,Timeunit unit)
- lockInterruptibly()放代碼塊中,調用interrupt()可中斷
- 加鎖是否公平
- synchronized是非公平鎖
- ReentrantLock兩者都可以,默認非公平鎖,可以通過構造器傳入
- 鎖綁定多條件Condition
- synchronized只有隨機喚醒或者全部喚醒
- ReentrantLock可以通過Condition實現分組喚醒和精確喚醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditonDemo {
}
/**
* A 打印5次 喚醒B 打印5次 再喚醒C打印5次
*/
class NumPrinter{
private ReentrantLock lock=new ReentrantLock();
Condition ca=lock.newCondition();
Condition cb=lock.newCondition();
Condition cc=lock.newCondition();
private char name='a';
public static void main(String[] args) {
NumPrinter numPrinter=new NumPrinter();
new Thread(numPrinter::printa,"A").start();
new Thread(numPrinter::printb,"B").start();
new Thread(numPrinter::printc,"C").start();
}
public void printa(){
lock.lock();
try {
while (name!='a'){
ca.await();
}
for (int i = 0; i < 5; i++) {
System.out.println('a');
}
name='b';
cb.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}public void printb(){
lock.lock();
try {
while (name!='b'){
cb.await();
}
for (int i = 0; i < 5; i++) {
System.out.println('b');
}
name='c';
cc.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}public void printc(){
lock.lock();
try {
while (name!='c'){
cc.await();
}
for (int i = 0; i < 5; i++) {
System.out.println('c');
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}