併發程式設計--執行緒基礎
---
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(); } }