1. 程式人生 > >併發程式設計--執行緒基礎

併發程式設計--執行緒基礎

---
title: 併發程式設計--執行緒基礎
date: 2018-07-05 09:12:57
categories:
- 併發程式設計
---

<Excerpt in index | 首頁摘要>
<!-- more -->
<The rest of contents | 餘下全文>

併發程式設計--執行緒基礎

1. 當多個執行緒訪問某一個類(物件或方法)時:這個類始終都能表現正確的行為,那麼這個類(物件或方法)就是執行緒安全的
2. 多個執行緒多個鎖:每個執行緒都可以拿到自己指定的鎖,分別獲取鎖之後,執行synchronized修飾的方法體內容;
3. 物件鎖的同步和非同步問題;
4. 髒讀

一.執行緒安全


#### 1. 當多個執行緒訪問某一個類(物件或方法)時:這個類始終都能表現正確的行為,那麼這個類(物件或方法)就是執行緒安全的。
Synchronized :可以在任意物件以及方法上加鎖,而加鎖的這段程式碼成為“互斥區”/"臨界區"。
例項 thread01:
分析:
當多個執行緒訪問myThread的run方法時,以排隊的方式進行處理(這裡排對是按照 CPU分配的先後順序而定的),一個執行緒想要執行synchronized修飾的方法裡的程式碼:
* 1 嘗試獲得鎖
* 2 如果拿到鎖,執行synchronized程式碼體內容;拿不到鎖,這個執行緒就會不斷的嘗試獲得這把鎖,直到拿到為止,而且是多個執行緒同時去競爭這把鎖。(也就是會有鎖競爭的問題)

```java
/**
 * 執行緒安全概念:當多個執行緒訪問某一個類(物件或方法)時,這個物件始終都能表現出正確的行為,那麼這個類(物件或方法)就是執行緒安全的。
 * synchronized:可以在任意物件及方法上加鎖,而加鎖的這段程式碼稱為"互斥區"或"臨界區"
 * @@author Maozw
 *
 */
public class MyThread extends Thread{
	private int count = 5 ;
	//synchronized加鎖
	@Override
	public synchronized void run(){
		count--;
		System.out.println(currentThread().getName() + " count = "+ count);
	}

	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

  

 2. 多個執行緒多個鎖:每個執行緒都可以拿到自己指定的鎖,分別獲取鎖之後,執行synchronized修飾的方法體內容;

說明:

* 關鍵字synchronized取得的鎖都是物件鎖,而不是把一段程式碼(方法)當做鎖;
所以程式碼中哪個執行緒先執行synchronized關鍵字的方法,哪個執行緒就持有該方法所屬物件的鎖(Lock),兩個物件,兩個執行緒分別獲得是兩個不同的鎖,互不影響;
* 有一種情況列外:即靜態方法上加synchronized關鍵字:
在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
例項:MultiThread.java

/**
 * 關鍵字synchronized取得的鎖都是物件鎖,而不是把一段程式碼(方法)當做鎖,
 * 所以程式碼中哪個執行緒先執行synchronized關鍵字的方法,哪個執行緒就持有該方法所屬物件的鎖(Lock),
 * 在靜態方法上加synchronized關鍵字,表示鎖定.class類,類一級別的鎖(獨佔.class類)。
 * @author Maozw
 *
 */
public class MultiThread {

	private int num = 0;

	/** static */
	public synchronized void printNum(String tag){
		try {

			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}

			System.out.println("tag " + tag + ", num = " + num);

		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	//注意觀察run方法輸出順序
	public static void main(String[] args) {
		//倆個不同的物件
		final MultiThread m1 = new MultiThread();
		final MultiThread m2 = new MultiThread();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				m2.printNum("b");
			}
		});		

		t1.start();
		t2.start();

	}
}

 

 3. 物件鎖的同步和非同步問題

* 同步:synchronized
同步的概念就是共享,其實需要記住執行緒要"共享"變數就可以,如果沒有共享變數就無所謂同步;
* 非同步:asynchronized
非同步的概念就是獨立:相互之間不受制約。
同步的目的就是為了執行緒安全:對於執行緒安全無非就是滿足倆特性:
* 原子性
* 可見性

例項MyObject.java
說明:
* t1執行緒先持有object物件的Lock鎖,t2執行緒可以以非同步的方式呼叫物件中的非synchronized修飾的方法
* t1執行緒先持有object物件的Lock鎖,t2執行緒如果在這個時候呼叫物件中的同步(synchronized)方法則需等待,也就是同步

/**
 * 物件鎖的同步和非同步問題
 * @@author Maozw
 *
 */
public class MyObject {

	public synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/** synchronized */
	public void method2(){
			System.out.println(Thread.currentThread().getName());
	}

	public static void main(String[] args) {

		final MyObject mo = new MyObject();

		/**
		 * 分析:
		 * t1執行緒先持有object物件的Lock鎖,t2執行緒可以以非同步的方式呼叫物件中的非synchronized修飾的方法
		 * t1執行緒先持有object物件的Lock鎖,t2執行緒如果在這個時候呼叫物件中的同步(synchronized)方法則需等待,也就是同步
		 */
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method1();
			}
		},"t1");

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method2();
			}
		},"t2");

		t1.start();
		t2.start();
	}
}

  

 

4. 髒讀

對於物件的同步和非同步方法,我們設計程式一定要考慮整體,不然會出現資料不一致的問題,經典Demo就是髒讀
例項:DirtyRead.java
說明:
* 在對一個物件的方法進行加鎖的時候,需要考慮業務的整體性,即為setValue/getValue方法同事加鎖synchronized同步關鍵字,保證業務的原子性,不然會出現資料不一致的問題

/**
 * 業務整體需要使用完整的synchronized,保持業務的原子性。
 * @@author Maozw
 *
 */
public class DirtyRead {

	private String username = "bjsxt";
	private String password = "123";

	public synchronized void setValue(String username, String password){
		this.username = username;

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

		this.password = password;

		System.out.println("setValue最終結果:username = " + username + " , password = " + password);
	}

	public void getValue(){
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}


	public static void main(String[] args) throws Exception{

		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("z3", "456");		
			}
		});
		t1.start();
		Thread.sleep(1000);

		dr.getValue();
	}
}