多線程學習-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教育課後總結。

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