多線程與高併發篇二

1.思考題:同步方法和非同步方法是否可以同時調用?

答案:可以

public class T {

	public synchronized void m1() { 
		System.out.println(Thread.currentThread().getName() + " m1 start...");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m1 end");
	}
	
	public void m2() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m2 ");
	}
	
	public static void main(String[] args) {
		T t = new T();
		
		/*new Thread(()->t.m1(), "t1").start();
		new Thread(()->t.m2(), "t2").start();*/
		
		new Thread(t::m1, "t1").start();
		new Thread(t::m2, "t2").start();
		
		/*
		//1.8之前的寫法
		new Thread(new Runnable() {

			@Override
			public void run() {
				t.m1();
			}
			
		});
		*/
		
	}
	
}

執行結果:

思考:如果兩個方法同時加了synchronized鎖,當m1()睡眠的時候,m2()可以同步調用嗎?

答案:不可以

運行結果:

同步方法和非同步方法可以同時調用的原因是因爲syc對於m1()方法加了鎖,但是m2()是一個無鎖的方法,所以訪問是不需要拿到對象所持有的鎖的,所以可以同時訪問。但是當兩個都是同步方法的時候,對象只有一個,多個線程訪問時只有順序執行。

2.模擬銀行賬戶:對業務寫方法加鎖,對業務讀方法不加鎖這樣可以不?

答案:容易產生髒讀問題(dirtyRead)

public class Account {
	String name;
	double balance;
	
	public synchronized void set(String name, double balance) {
		this.name = name;

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		
		this.balance = balance;
	}
	
	public /*synchronized*/ double getBalance(String name) {
		return this.balance;
	}
	
	
	public static void main(String[] args) {
		Account a = new Account();
		new Thread(()->a.set("zhangsan", 100.0)).start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a.getBalance("zhangsan"));
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a.getBalance("zhangsan"));
	}
}

3.synchronized爲什麼可重入?

什麼是可重入鎖?

若一個程序或子程序可以“在任意時刻被中斷然後操作系統調度執行另外一段代碼,這段代碼又調用了該子程序不會出錯”,則稱其爲可重入(reentrant或re-entrant)的。即當該子程序正在運行時,執行線程可以再次進入並執行它,仍然獲得符合設計時預期的結果。與多線程併發執行的線程安全不同,可重入強調對單個線程執行時重新進入同一個子程序仍然是安全的。

通俗來說:當線程A請求一個由線程B持有的對象鎖時,該線程會阻塞,而當線程B請求由自己持有的對象鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞。

我先給大家一個結論:synchronized 是可重入鎖!

假設我們現在不知道它是不是一個可重入鎖,那我們就應該想方設法來驗證它是不是可重入鎖?怎麼驗證呢?看下面的代碼!

public class Xttblog extends SuperXttblog {
    public static void main(String[] args) {
        Xttblog child = new Xttblog();
        child.doSomething();
    }
 
    public synchronized void doSomething() {
        System.out.println("child.doSomething()" + Thread.currentThread().getName());
        doAnotherThing(); // 調用自己類中其他的synchronized方法
    }
 
    private synchronized void doAnotherThing() {
        super.doSomething(); // 調用父類的synchronized方法
        System.out.println("child.doAnotherThing()" + Thread.currentThread().getName());
    }
}
 
class SuperXttblog {
    public synchronized void doSomething() {
        System.out.println("father.doSomething()" + Thread.currentThread().getName());
    }
}

上面的代碼也不是隨便寫的,我是根據維基百科的定義寫出這段代碼來驗證它。現在運行一下上面的代碼,我們看一下結果:

child.doSomething()Thread-5492
father.doSomething()Thread-5492
child.doAnotherThing()Thread-5492

現在可以驗證出 synchronized 是可重入鎖了吧!因爲這些方法輸出了相同的線程名稱,表明即使遞歸使用synchronized也沒有發生死鎖,證明其是可重入的。

還看不懂?那我就再解釋下!

這裏的對象鎖只有一個,就是 child 對象的鎖,當執行 child.doSomething 時,該線程獲得 child 對象的鎖,在 doSomething 方法內執行 doAnotherThing 時再次請求child對象的鎖,因爲synchronized 是重入鎖,所以可以得到該鎖,繼續在 doAnotherThing 裏執行父類的 doSomething 方法時第三次請求 child 對象的鎖,同樣可得到。如果不是重入鎖的話,那這後面這兩次請求鎖將會被一直阻塞,從而導致死鎖。

所以在 java 內部,同一線程在調用自己類中其他 synchronized 方法/塊或調用父類的 synchronized 方法/塊都不會阻礙該線程的執行。就是說同一線程對同一個對象鎖是可重入的,而且同一個線程可以獲取同一把鎖多次,也就是可以多次重入。因爲java線程是基於“每線程(per-thread)”,而不是基於“每調用(per-invocation)”的(java中線程獲得對象鎖的操作是以線程爲粒度的,per-invocation 互斥體獲得對象鎖的操作是以每調用作爲粒度的)。

可重入鎖的實現原理?

看到這裏,你終於明白了 synchronized 是一個可重入鎖。但是面試官要再問你,可重入鎖的原理是什麼?

對不起,你又卡殼了。

那麼我現在先給你說一下,可重入鎖的原理。具體我們後面再寫 ReentrantLock 的時候來驗證或看它源碼。

重入鎖實現可重入性原理或機制是:每一個鎖關聯一個線程持有者和計數器,當計數器爲 0 時表示該鎖沒有被任何線程持有,那麼任何線程都可能獲得該鎖而調用相應的方法;當某一線程請求成功後,JVM會記下鎖的持有線程,並且將計數器置爲 1;此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;當線程退出同步代碼塊時,計數器會遞減,如果計數器爲 0,則釋放該鎖。

參考:https://www.xttblog.com/?p=3218

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章