1、多線程基礎知識--傳統線程機制的回顧java
一、傳統使用類Thread和接口Runnable實現面試
1):在Thread子類覆蓋的run方法中編寫運行代碼數據庫
2):在傳遞給Thread對象的Runnable對象的run方法中編寫代碼多線程
3):總結併發
查看Thread類的run()方法的源代碼,能夠看到其實這兩種方式都是在調用Thread對象的run方法,若是Thread的run方法沒有被覆蓋,而且爲該Thread對象設置了一個Runnable對象,該run方法會調用Runnable對象的run方法。dom
二、定時器Timer和TimerTask異步
Timer在實際開發中應用場景很少,通常來講都會用其餘第三方庫來實現。但有時會在一些面試題中出現。ide
1):模擬雙重定時器測試
三、線程互斥與同步this
在引入多線程後,因爲線程執行的異步性,會給系統形成混亂,特別是在急用臨界資源是,如多個線程急用同一臺打印機,會是打印結果交織在一塊兒,難於區分。當多個線程急用共享變量,表格,鏈表時,可能會致使數據處理出錯,所以線程同步的主要內容是使併發執行的各線程之間可以有效的共享資源和互相合做,從而使程序的執行具備可再現性
當線程併發執行時,因爲資源共享和線程協做,使得線程之間會存在如下兩種制約關係。
1):間接相互制約。一個系統中的多個線程必然要共享某種系統資源,如共享CPU,共享I/O設備,所謂間接制約即源於這種資源共享,打印機就是最好的例子,線程A在使用打印機時,其它線程都要等待。
2):直接相互制約。這種制約主要時由於線程之間的合做,若有線程A將計算結果提供給線程B做進一步處理,那麼線程B在線程A將數據送達以前都將會處於阻塞狀態。
間接相互制約能夠稱爲互斥,直接相互制約能夠稱爲同步,對於互斥能夠這樣理解,線程A和線程B互斥訪問某個資源則它們之間就會產生順序問題——要麼線程A等待線程B操做完畢,要麼線程B等待線程操做完畢,這其實就是線程的同步了。所以同步包括互斥、互斥實際上是一種特殊的同步。
例子:
四、線程局部變量ThreadLocal
A:ThreadLocal的做用和目的:用於實現線程內的數據共享,即對於相同的程序代碼,多個模塊在同一個線程中運行時要共享一份數據,而在另外線程中運行時又共享另一份數據。
B:每一個線程調用全局ThreadLocal對象的set方法,在set方法中,首先根據當前線程獲取當前線程的ThreadLocalMap對象,而後往這個map中插入一條記錄,key實際上是ThreadLocal對象,value是各自的set方法傳進去的值。也就是每一個線程其實都有一份本身獨享的ThreadLocalMap對象,該對象的key是ThreadLocal對象,值是用戶設置的具體值。在線程結束時能夠調用ThreadLocal.remove()方法,這樣會更快釋放內存,不調用也能夠,由於線程結束後也能夠自動釋放相關的ThreadLocal變量。
C:ThreadLocal的應用場景:
➢ 訂單處理包含一系列操做:減小庫存量、增長一條流水臺帳、修改總帳,這幾個操做要在同一個事務中完成,一般也即同一個線程中進行處理,若是累加公司應收款的操做失敗了,則應該把前面的操做回滾,不然,提交全部操做,這要求這些操做使用相同的數據庫鏈接對象,而這些操做的代碼分別位於不一樣的模塊類中。
➢ 銀行轉帳包含一系列操做: 把轉出賬戶的餘額減小,把轉入賬戶的餘額增長,這兩個操做要在同一個事務中完成,它們必須使用相同的數據庫鏈接對象,轉入和轉出操做的代碼分別是兩個不一樣的賬戶對象的方法。
➢ 例如 Strut2 的 ActionContext,同一段代碼被不一樣的線程調用運行時,該代碼操做的數據是每一個線程各自的狀態和數據,對於不一樣的線程來講,getContext 方法拿到的對象都不相同,對同一個線程來講,無論調getContext 方法多少次和在哪一個模塊中 getContext 方法,拿到的都是同一個。
1):在多線程的類(如ThreadDemo類)中,建立一個ThreadLocal對象threadXxx,用來保存線程間須要隔離的對象xxx。
2):在ThreadDemo類中,建立一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象爲null的時候,應該new一個隔離訪問類型的對象,並強制轉換爲要應用的類型。
3):在ThreadDemo類的run()方法中,經過調用getXxx()方法獲取要操做的數據,這樣能夠保證每一個線程對應一個數據對象,在任什麼時候刻都操做的是這個對象。
import java.util.Random; public class ThreadLocalTest implements Runnable{ ThreadLocal<Student> studentThreadLocal = new ThreadLocal<>(); @Override public void run() { String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName+"is running..."); Random random = new Random(); int age = random.nextInt(100); System.out.println(currentThreadName+"is set age:"+age); //經過這個方法,爲每一個線程都獨立的new一個student對象,每一個線程的student對象均可以設置不一樣的值 Student student = getStudent(); student.setAge(age); System.out.println(currentThreadName+"is first get age:"+student.getAge()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(currentThreadName+"is second get age:"+student.getAge()); } private Student getStudent() { Student student = studentThreadLocal.get(); if(null==student){ student = new Student(); studentThreadLocal.set(student); } return student; } public static void main(String[] args) { ThreadLocalTest t = new ThreadLocalTest(); Thread t1 = new Thread(t,"Thread A "); Thread t2 = new Thread(t,"Thread B "); t1.start(); t2.start(); } } class Student{ private String name; private Integer age; public Student(String name, Integer age) { this.name = name; this.age = age; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
運行結果以下:
Thread B is running... Thread A is running... Thread B is set age:70 Thread A is set age:75 Thread A is first get age:75 Thread B is first get age:70 Thread A is second get age:75 Thread B is second get age:70
在Java傳統機制中的共享數據方式,大體能夠簡單分爲兩種狀況:
1):多個線程行爲一致,共同操做一個數據源。也就是每一個線程執行的代碼相同,可使用同一個Runnable對象,這個Runnable對象中有那個共享數據,例如,賣票系統。
2):多個線程行爲不一致,共同操做一個數據源。也就是每一個線程執行的代碼不一樣,這是須要用不用的Runnable對象。例如,銀行存取款。
下面咱們經過兩個示例代碼來分別說明這兩種方式:
一、多個線程行爲一致共同操做一個數據
若是每一個線程執行的代碼相同,可使用同一個Runnable對象,這個Runnable對象中有那個共享數據,例如,買票系統。
/** * 共享數據類 */ class ShareData1 { private int num = 10; public synchronized void inc() { num++; System.out.println(Thread.currentThread().getName() + ": invoke inc method num = " + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 多線程類 */ public class RunnableCusToInc implements Runnable { private ShareData1 shareData; public RunnableCusToInc(ShareData1 shareData) { this.shareData=shareData; } @Override public void run() { for (int i = 0; i < 5; i++) { shareData.inc(); } } /** * 測試方法 */ public static void main(String[] args) { ShareData1 shareData = new ShareData1(); for(int i=0;i<4;i++){ new Thread(new RunnableCusToInc(shareData),"Thread"+i).start(); } } }
運行結果以下:
Thread0: invoke inc method num = 11 Thread0: invoke inc method num = 12 Thread0: invoke inc method num = 13 Thread0: invoke inc method num = 14 Thread0: invoke inc method num = 15 Thread3: invoke inc method num = 16 Thread3: invoke inc method num = 17 Thread3: invoke inc method num = 18 Thread3: invoke inc method num = 19 Thread3: invoke inc method num = 20 Thread2: invoke inc method num = 21 Thread2: invoke inc method num = 22 Thread2: invoke inc method num = 23 Thread2: invoke inc method num = 24 Thread2: invoke inc method num = 25 Thread1: invoke inc method num = 26 Thread1: invoke inc method num = 27 Thread1: invoke inc method num = 28 Thread1: invoke inc method num = 29 Thread1: invoke inc method num = 30
二、多個線程行爲不一致共同操做一個數據
若是每一個線程執行的代碼不一樣,這時候須要用不用的Runnable對象,有以下兩種方式來實現這些Runnable對象之間的數據共享。
1)將共享數據封裝在另一個對象中,而後將這個對象逐一傳遞個各個Runnable對象。每一個線程對共享數據的操做方法也分配到那個對象身上去完成,這樣容易實現針對該數據進行的各個操做的互斥和通訊。
/** * 共享數據類 */ class ShareData1 { private int num = 10; public synchronized void inc() { num++; System.out.println(Thread.currentThread().getName() + ": invoke inc method num = " + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void dec() { num--; System.out.println(Thread.currentThread().getName() + ": invoke dec method num = " + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 測試方法 */ public static void main(String[] args) { ShareData1 shareData = new ShareData1(); for (int i = 0; i < 4; i++) { if (i % 2 == 0) { new Thread(new RunnableCusToInc(shareData), "Thread" + i).start(); } else { new Thread(new RunnableCusToDec(shareData), "Thread" + i).start(); } } } } /** * 多線程類 */ class RunnableCusToInc implements Runnable { //封裝共享數據 private ShareData1 shareData; public RunnableCusToInc(ShareData1 shareData) { this.shareData = shareData; } @Override public void run() { for (int i = 0; i < 5; i++) { shareData.inc(); } } } class RunnableCusToDec implements Runnable { //封裝共享數據 private ShareData1 shareData; public RunnableCusToDec(ShareData1 shareData) { this.shareData = shareData; } @Override public void run() { for (int i = 0; i < 5; i++) { shareData.dec(); } } }
運行結果以下:
Thread0: invoke inc method num = 11 Thread0: invoke inc method num = 12 Thread0: invoke inc method num = 13 Thread0: invoke inc method num = 14 Thread0: invoke inc method num = 15 Thread3: invoke dec method num = 14 Thread3: invoke dec method num = 13 Thread3: invoke dec method num = 12 Thread3: invoke dec method num = 11 Thread3: invoke dec method num = 10 Thread2: invoke inc method num = 11 Thread2: invoke inc method num = 12 Thread2: invoke inc method num = 13 Thread2: invoke inc method num = 14 Thread2: invoke inc method num = 15 Thread1: invoke dec method num = 14 Thread1: invoke dec method num = 13 Thread1: invoke dec method num = 12 Thread1: invoke dec method num = 11 Thread1: invoke dec method num = 10
2):將這些Runnable對象做爲某一個類中的內部類,共享數據做爲這個外部類中的成員變量,每一個線程對共享數據的操做方法也分配給外部類,以便實現對共享數據進行的各個操做的互斥和通訊,做爲內部類的各個Runnable對象調用外部類的這些方法。
public class InnerThreadRunnable { public static void main(String[] args) { final ShareData2 shareData = new ShareData2(); for(int i=0;i<4;i++){ if(i%2==0){ new Thread(new Runnable() { @Override public void run() { shareData.inc(); } },"Thread"+i).start(); }else{ new Thread(new Runnable() { @Override public void run() { shareData.dec(); } },"Thread"+i).start(); } } } } class ShareData2{ private int num = 10 ; public synchronized void inc() { num++; System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void dec() { num--; System.err.println(Thread.currentThread().getName()+": invoke dec method num =" + num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
運行結果以下:
Thread0: invoke inc method num =11 Thread3: invoke dec method num =10 Thread2: invoke inc method num =11 Thread1: invoke dec method num =10
補充:上面兩種方式的組合:將共享數據封裝在另一個對象中,每一個線程對共享數據的操做方法也分配到那個對象身上去完成,對象做爲這個外部類中的成員變量或方法中的局部變量,每一個線程的 Runnable 對象做爲外部類中的成員內部類或局部內部類。
總之,要同步互斥的幾段代碼最好是分別放在幾個獨立的方法中,這些方法再放在同一個類中,這樣比較容易實現它們之間的同步互斥和通訊。