1. 程式人生 > >多執行緒學習-day-03synchronized內建鎖

多執行緒學習-day-03synchronized內建鎖

執行緒基礎、執行緒之間的共享和協作

(目前會將一些概念簡單描述,一些重點的點會詳細描述)

執行緒常用方法和執行緒的狀態

        start():呼叫start()方法後,使執行緒從新建狀態處於就緒狀態。

        sleep():呼叫sleep()方法後,設定休眠時間,使執行緒從執行狀態處於阻塞(休眠)狀態,休眠時間到,執行緒從阻塞狀態轉變為就緒狀態。

        wait():呼叫wait()方法後,使執行緒從執行狀態處於阻塞(休眠)狀態,只有通過notify()或者notifyAll()方法重新使執行緒處於就緒狀態。(後續補充notify()和notifyAll()方法)。

        interrupt():呼叫interrupt()方法後,不是強制關閉執行緒,只是跟執行緒打個招呼,將執行緒的中斷標誌位置為true,執行緒是否中斷,由執行緒本身決定。

        isInterrypt():執行緒中斷標誌位,true/false兩個Boolean值,用來判斷是否呼叫interrupt()方法,告訴執行緒是否中斷。

        interrupted():判斷執行緒是否處於中斷狀態,並將中斷標誌位改為false。

        run():執行執行緒的方法。

 

synchronized內建鎖

1、用處

        synchronized作為執行緒同步的關鍵字,設計到的概念,下面就對鎖的概念進行詳細介紹。

        Java內建鎖是一個互斥鎖,這就說明最多隻有一個執行緒能夠獲得該鎖,例如兩個執行緒:執行緒A和執行緒B,如果執行緒A嘗試去獲得執行緒B的內建鎖,則執行緒A必須等待或者阻塞,直到執行緒B釋放這個鎖為止;如果執行緒B永不釋放這個鎖,則執行緒A則永遠處於等待或阻塞狀態。

        Java的物件鎖和類鎖在鎖的概念上,與內建鎖幾乎是一致的,但是物件鎖和類鎖的區別是非常大的。

2、物件鎖

        用synchronized修飾非靜態方法、用synchronized(this)作為同步程式碼塊、用synchronized(非this物件)的用法鎖的是物件,執行緒想要執行對應的同步程式碼,需要先獲得物件鎖。

3、類鎖

        用synchronized修飾靜態方法、用synchronized(類.class)的用法鎖的是類,執行緒想要執行對應的同步程式碼,需要先獲得類鎖。

 

以下對synchronized關鍵字的用法,物件鎖,類鎖用實際程式碼為例子進行介紹:

1、先看一個非執行緒安全的例項,看看synchronized的用途

public class SynRun {

	public static void main(String[] args) {
		// 定義HasSelfNum物件
		HasSelfNum hasSelfNum = new HasSelfNum();

		// 定義ThreadZS多執行緒類
		ThreadZS threadZS = new ThreadZS(hasSelfNum);
		threadZS.start();

		// 定義ThreadLS多執行緒類
		ThreadLS threadLS = new ThreadLS(hasSelfNum);
		threadLS.start();
	}

}

// 定義一個類HasSelfNum,作為同步設定num的變化
class HasSelfNum {
	// 定義一個變數num
	private int num = 0;

	// 定義一個類的方法addNum,並傳一個字串作為形參
	public void addNum(String name) {
		try {
			if (name.equals("zs")) {
				num = 100;
				System.out.println("zs設定了num引數...");
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("ls設定了num引數...");
			}
			// 將num引數輸出
			System.out.println(name + " 設定的 num = " + num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

// 定義一個多執行緒類ThreadZS
class ThreadZS extends Thread {
	// 定義HasSelfNum
	private HasSelfNum hasSelfNum;

	// 設定建構函式,將物件賦值
	public ThreadZS(HasSelfNum hasSelfNum) {
		super();
		this.hasSelfNum = hasSelfNum;
	}

	// 定義run方法
	@Override
	public void run() {
		super.run();
		hasSelfNum.addNum("zs");
	}
}

// 定義一個多執行緒類ThreadLS
class ThreadLS extends Thread {
	// 定義HasSelfNum
	private HasSelfNum hasSelfNum;

	// 設定建構函式,將物件賦值
	public ThreadLS(HasSelfNum hasSelfNum) {
		super();
		this.hasSelfNum = hasSelfNum;
	}

	// 定義run方法
	@Override
	public void run() {
		super.run();
		hasSelfNum.addNum("ls");
	}
}

控制檯輸出結果:
zs設定了num引數...
ls設定了num引數...
ls 設定的 num = 200
zs 設定的 num = 200

看到輸出結果是非執行緒安全的。

接下來將HasSelfNum類裡面的addNum()方法加上synchronized關鍵字,其餘程式碼不變

// 定義一個類HasSelfNum,作為同步設定num的變化
class HasSelfNum {
	// 定義一個變數num
	private int num = 0;

	// 定義一個類的方法addNum,並傳一個字串作為形參,加上了synchronized關鍵字
	public synchronized void addNum(String name) {
		try {
			if (name.equals("zs")) {
				num = 100;
				System.out.println("zs設定了num引數...");
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("ls設定了num引數...");
			}
			// 將num引數輸出
			System.out.println(name + " 設定的 num = " + num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

執行後,控制檯輸出結果:
zs設定了num引數...
zs 設定的 num = 100
ls設定了num引數...
ls 設定的 num = 200

由此可看出,兩個執行緒訪問同一個物件中的同步方法一定是執行緒安全的。因為物件的方法加上了synchronized關鍵字成為同步方法,所以先把zs打印出來,再把ls打印出來。

 

2、不同執行緒呼叫不同物件

只修改了main方法裡面的寫法,其餘程式碼不變

public static void main(String[] args) {

    // 定義2個HasSelfNum物件
    HasSelfNum hasSelfNum1 = new HasSelfNum();
    HasSelfNum hasSelfNum2 = new HasSelfNum();

    // 定義ThreadZS多執行緒類,用hasSelfNum1物件的方法
    ThreadZS threadZS = new ThreadZS(hasSelfNum1);
    threadZS.start();

    // 定義ThreadLS多執行緒類,用hasSelfNum2物件的方法
    ThreadLS threadLS = new ThreadLS(hasSelfNum2);
    threadLS.start();
}

控制檯輸出結果:
zs設定了num引數...
ls設定了num引數...
ls 設定的 num = 200
zs 設定的 num = 100

可以看出輸出結果是非同步的,因為執行緒threadZS獲得的是hasSelfNum1的物件鎖,threadLS獲得的是hasSelfNum2的物件鎖,他們沒有獲得同一個物件鎖,沒有出現競爭情況,因此是非同步的結果

 

3、運用synchronized(tihs)同步程式碼塊

public class SynRun1 {

	public static void main(String[] args) {
		// 初始化ShowTime物件
		ShowTime showTime = new ShowTime();

		// 初始化ThreadA多線層物件
		ThreadA threadA = new ThreadA(showTime);
		threadA.start();

		// 初始化TreadB多執行緒物件
		ThreadB threadB = new ThreadB(showTime);
		threadB.start();
	}

}

// 定義一個類ShowTime
class ShowTime {

	// 定義一個顯式時間方法showTime
	public void showTime() {
		// 利用synchronized(this)同步程式碼塊作為同步方法
		try {
			synchronized (this) {
				System.out.println("一個程序開始執行,執行時間為:" + System.currentTimeMillis());
				// 設定休眠時間
				Thread.sleep(2000);
				System.out.println("這個程序執行結束,結束時間為:" + System.currentTimeMillis());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

// 定義一個執行緒A
class ThreadA extends Thread {
	// 定義ShowTime物件
	private ShowTime showTime;

	// 定義有餐建構函式
	public ThreadA(ShowTime showTime) {
		super();
		this.showTime = showTime;
	}

	// 定義run方法
	@Override
	public void run() {
		super.run();
		showTime.showTime();
	}
}

// 定義一個執行緒B
class ThreadB extends Thread {
	// 定義ShowTime物件
	private ShowTime showTime;

	// 定義有餐建構函式
	public ThreadB(ShowTime showTime) {
		super();
		this.showTime = showTime;
	}

	// 定義run方法
	@Override
	public void run() {
		super.run();
		showTime.showTime();
	}
}

控制檯輸出結果為:
一個程序開始執行,執行時間為:1539525103617
這個程序執行結束,結束時間為:1539525105619
一個程序開始執行,執行時間為:1539525105619
這個程序執行結束,結束時間為:1539525107619

結果顯示也是同步的,執行緒獲取的是synchronized(this){}括號裡面的物件例項的物件鎖。

 

4、運用synchronized(非this物件)

public class SynRun1 {

	public static void main(String[] args) {
		ShowInfo service = new ShowInfo("Cansluck");

		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();

		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
	}
}

class ShowInfo {

	String info = new String();

	public ShowInfo(String info) {
		this.info = info;
	}

	public void showInfo() {
		try {
			synchronized (info) {
				System.out.println(
						"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入同步塊");
				Thread.sleep(3000);
				System.out.println(
						"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開同步塊");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadA extends Thread {
	private ShowInfo showInfo;

	public ThreadA(ShowInfo showInfo) {
		super();
		this.showInfo = showInfo;
	}

	@Override
	public void run() {
		showInfo.showInfo();

	}

}

class ThreadB extends Thread {

	private ShowInfo showInfo;

	public ThreadB(ShowInfo showInfo) {
		super();
		this.showInfo = showInfo;
	}

	@Override
	public void run() {
		showInfo.showInfo();

	}

}

控制檯輸出結果:
執行緒名稱為:A在1539525475324進入同步塊
執行緒名稱為:A在1539525478325離開同步塊
執行緒名稱為:B在1539525478325進入同步塊
執行緒名稱為:B在1539525481325離開同步塊

這裡執行緒爭奪的是info的物件鎖,兩個執行緒有競爭同一物件鎖的關係,出現同步

 

5、靜態synchronized同步方法

public class SynRun {

	public static void main(String[] args) {

		ThreadAA a = new ThreadAA();
		a.setName("A");
		a.start();

		ThreadBB b = new ThreadBB();
		b.setName("B");
		b.start();

	}

}

class Service {

	synchronized public static void printA() {
		try {
			System.out.println(
					"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
			Thread.sleep(3000);
			System.out.println(
					"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	synchronized public static void printB() {
		System.out.println("執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
		System.out.println("執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
	}

}

class ThreadAA extends Thread {
	@Override
	public void run() {
		Service.printA();
	}

}

class ThreadBB extends Thread {
	@Override
	public void run() {
		Service.printB();
	}
}

控制檯輸出結果:
執行緒名稱為:B在1539525627704進入printB
執行緒名稱為:B在1539525627704離開printB
執行緒名稱為:A在1539525627704進入printA
執行緒名稱為:A在1539525630705離開printA

兩個執行緒在爭奪同一個類鎖,因此同步

 

6、運用synchronized (類.class)

public class SynRun {

	public static void main(String[] args) {

		ThreadAA a = new ThreadAA();
		a.setName("A");
		a.start();

		ThreadBB b = new ThreadBB();
		b.setName("B");
		b.start();

	}

}

class Service {

	public static void printA() {
		synchronized (Service.class) {
			try {
				System.out.println(
						"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
				Thread.sleep(3000);
				System.out.println(
						"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

	public static void printB() {
		synchronized (Service.class) {
			System.out.println(
					"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
			System.out.println(
					"執行緒名稱為:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
		}
	}
}

class ThreadAA extends Thread {
	@Override
	public void run() {
		Service.printA();
	}

}

class ThreadBB extends Thread {
	@Override
	public void run() {
		Service.printB();
	}
}

控制檯輸出結果:
執行緒名稱為:A在1539525736333進入printA
執行緒名稱為:A在1539525739334離開printA
執行緒名稱為:B在1539525739334進入printB
執行緒名稱為:B在1539525739334離開printB

兩個執行緒依舊在爭奪同一個類鎖,因此同步

需要特別說明:對於同一個類A,執行緒1爭奪A物件例項的物件鎖,執行緒2爭奪類A的類鎖,這兩者不存在競爭關係。物件鎖和類鎖互互不干預

靜態方法則一定會同步,非靜態方法需在單例模式才生效,但是也不能都用靜態同步方法,總之用得不好可能會給效能帶來極大的影響。另外,有必要說一下的是Spring的bean預設是單例的

物件鎖:鎖的是類的物件例項。

類鎖 :鎖的是每個類的的Class物件,每個類的的Class物件在一個虛擬機器中只有一個,所以類鎖也只有一個。

來自享學IT教育課後總結。