java基礎學習_多線程02_多線程、設計模式_day24總結
=============================================================================
=============================================================================
涉及到的知識點有:
1:多線程(了解)
(1)JDK5中Lock鎖的使用
(2)Lock接口的方法
(3)死鎖問題的描述和代碼展現
(4)生産者和消費者多線程展現(線程間通信問題)
(5)線程的狀态轉換圖及常見的線程執行情況
(6)線程組
(7)線程池
(8)多線程實作的第三種方案:依賴于線程池而存在的
(9)匿名内部類方式使用多線程
(10)定時器
(11)多線程常見的面試題
2:設計模式(了解)
(1)面向對象思想的設計原則的概述
(2)設計模式
(3)常見的設計模式
(4)Runtime類的概述和應用
=============================================================================
=============================================================================
1:多線程(了解)
(1)JDK5中Lock鎖的使用
雖然我們可以了解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪裡加上了鎖,在哪裡釋放了鎖,
為了更清晰的表達如何加鎖和釋放鎖,JDK5以後提供了一個新的鎖對象Lock接口。
即:JDK5以後的針對線程的鎖定操作和釋放操作。
Lock實作提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。
(2)Lock接口的方法
void lock() 擷取鎖(加鎖)
void unlock() 釋放鎖
ReentrantLock類是Lock接口的實作類。
示例代碼如下:
1 package cn.itcast_01;
2 /*
3 * 雖然我們可以了解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪裡加上了鎖,在哪裡釋放了鎖,
4 * 為了更清晰的表達如何加鎖和釋放鎖,JDK5以後提供了一個新的鎖對象Lock接口。
5 *
6 * Lock接口的方法:
7 * void lock() 擷取鎖(加鎖)
8 * void unlock() 釋放鎖
9 *
10 * ReentrantLock類是Lock接口的實作類。
11 */
12 public class SellTicketDemo {
13 public static void main(String[] args) {
14 // 建立資源對象
15 SellTicket st = new SellTicket();
16
17 // 建立三個線程對象
18 Thread t1 = new Thread(st, "視窗1");
19 Thread t2 = new Thread(st, "視窗2");
20 Thread t3 = new Thread(st, "視窗3");
21
22 // 啟動線程
23 t1.start();
24 t2.start();
25 t3.start();
26 }
27 }
SellTicketDemo.java
1 package cn.itcast_01;
2
3 import java.util.concurrent.locks.Lock;
4 import java.util.concurrent.locks.ReentrantLock;
5
6 public class SellTicket implements Runnable {
7 // 定義票
8 private int tickets = 100;
9
10 // 定義鎖對象
11 private Lock lock = new ReentrantLock(); // 多态
12
13 @Override
14 public void run() {
15 while (true) {
16 // 如果鎖裡面有異常發生,釋放鎖lock.unlock();就不會被執行,
17 // 為了保證釋放鎖lock.unlock();被執行,使用try...finally...
18 // 将lock.unlock();放在finally裡面。
19 try {
20 // 擷取鎖(加鎖)
21 lock.lock();
22 if (tickets > 0) {
23 try {
24 Thread.sleep(100);
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }
28 System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
29 }
30 } finally {
31 // 釋放鎖
32 lock.unlock();
33 }
34 }
35 }
36
37 }
SellTicket.java
--------------------------------------
(3)死鎖問題的描述和代碼展現
同步弊端:效率低;如果出現了同步嵌套,就容易産生死鎖問題。
死鎖問題:是指兩個或者兩個以上的線程在執行的過程中,因争奪資源産生的一種互相等待現象。
同步代碼塊的嵌套案例代碼如下:
1 package cn.itcast_02;
2
3 public class MyLock {
4 // 建立兩把鎖對象
5 public static final Object objA = new Object();
6 public static final Object objB = new Object();
7 }
1 package cn.itcast_02;
2
3 public class DieLock extends Thread {
4
5 private boolean flag;
6
7 public DieLock(boolean flag) {
8 this.flag = flag;
9 }
10
11 @Override
12 public void run() {
13 if (flag) {
14 synchronized (MyLock.objA) {
15 System.out.println("if objA");
16 synchronized (MyLock.objB) {
17 System.out.println("if objB");
18 }
19 }
20 } else {
21 synchronized (MyLock.objB) {
22 System.out.println("else objB");
23 synchronized (MyLock.objA) {
24 System.out.println("else objA");
25 }
26 }
27 }
28 }
29
30 }
1 package cn.itcast_02;
2
3 /*
4 * 同步的弊端:
5 * A:效率低
6 * B:如果出現了同步嵌套,就容易産生死鎖問題
7 *
8 * 死鎖:
9 * 是指兩個或者兩個以上的線程在執行的過程中,因争奪資源産生的一種互相等待現象。
10 *
11 * 舉例:
12 * 中國人和美國人一起吃飯案例。
13 * 正常情況:
14 * 中國人:筷子兩支
15 * 美國人:刀和叉
16 * 現在:
17 * 中國人:筷子1支,刀一把
18 * 美國人:筷子1支,叉一把
19 */
20 public class DieLockDemo {
21 public static void main(String[] args) {
22 DieLock dl1 = new DieLock(true);
23 DieLock dl2 = new DieLock(false);
24
25 dl1.start();
26 dl2.start();
27 }
28 }
--------------------------------------
(4)生産者和消費者多線程展現(線程間通信問題)
線程間通信:針對同一個資源的操作有不同種類的線程。
舉例:賣票有進的,也有出的。
通過設定線程(生産者)和擷取線程(消費者)針對同一個學生對象進行操作。
以學生作為資源來實作的。
學生資源類:Student
設定學生資料線程類:SetThread(生産者)
擷取學生資料線程類:GetThread(消費者)
測試類:StudentDemo
代碼:
A:最基本的版本,隻有一個資料。
B:改進版本,給出了不同的資料,并加入了同步機制。
C:等待喚醒機制改進該程式,讓資料能夠實作依次的出現。
wait()
notify() (用在單個生産線程和單個消費線程)
notifyAll() (用在多個生産線程和多個消費線程)
D:等待喚醒機制的代碼優化。把資料和操作都寫在了學生資源類中。
A版本代碼如下:
1 package cn.itcast_03;
2
3 public class Student {
4 String name;
5 int age;
6 }
Student.java
1 package cn.itcast_03;
2
3 public class SetThread implements Runnable {
4
5 private Student s;
6
7 // 帶參構造
8 public SetThread(Student s) {
9 this.s = s;
10 }
11
12 @Override
13 public void run() {
14 // Student s = new Student();
15 s.name = "林青霞";
16 s.age = 27;
17 }
18
19 }
SetThread.java
1 package cn.itcast_03;
2
3 public class GetThread implements Runnable {
4
5 private Student s;
6
7 // 帶參構造
8 public GetThread(Student s) {
9 this.s = s;
10 }
11
12 @Override
13 public void run() {
14 // Student s = new Student();
15 System.out.println(s.name + "---" + s.age);
16 }
17
18 }
GetThread.java
1 package cn.itcast_03;
2
3 /*
4 * 分析:
5 * 學生資源類:Student
6 * 設定學生資料線程類:SetThread(生産者)
7 * 擷取學生資料線程類:GetThread(消費者)
8 * 測試類:StudentDemo
9 *
10 * 問題1:按照思路寫代碼,發現資料每次都是:null---0
11 * 原因:
12 * 我們在每個線程中都建立了新的資源:Student s = new Student();
13 * 而我們要求的是:設定和擷取線程的資源應該是同一個。
14 * 如何解決呢?
15 * 如何把一個資料在多個類中共享使用呢?
16 * 思路1:在外界把這個資料建立出來,通過構造方法傳遞給其他的類。
17 *
18 */
19 public class StudentDemo {
20 public static void main(String[] args) {
21 // 建立資源對象
22 Student s = new Student();
23
24 // 建立設定和擷取對象,并把資源對象作為構造方法的參數進行傳遞
25 SetThread st = new SetThread(s);
26 GetThread gt = new GetThread(s);
27
28 // 建立線程對象
29 Thread t1 = new Thread(st);
30 Thread t2 = new Thread(gt);
31
32 // 啟動線程
33 t1.start();
34 t2.start();
35 }
36 }
B版本代碼如下:
1 package cn.itcast_04;
2
3 public class Student {
4 String name;
5 int age;
6 }
1 package cn.itcast_04;
2
3 public class SetThread implements Runnable {
4 private int x = 0;
5
6 private Student s;
7
8 // 帶參構造
9 public SetThread(Student s) {
10 this.s = s;
11 }
12
13 @Override
14 public void run() {
15 while (true) {
16 synchronized (s) {
17 if (x % 2 == 0) {
18 s.name = "林青霞"; // 剛走到這裡,就被别人搶到了執行權
19 s.age = 27;
20 } else {
21 s.name = "劉意"; // 剛走到這裡,就被别人搶到了執行權
22 s.age = 30;
23 }
24 x++;
25 }
26 }
27 }
28
29 }
1 package cn.itcast_04;
2
3 public class GetThread implements Runnable {
4
5 private Student s;
6
7 // 帶參構造
8 public GetThread(Student s) {
9 this.s = s;
10 }
11
12 @Override
13 public void run() {
14 while (true) {
15 synchronized (s) {
16 System.out.println(s.name + "---" + s.age);
17 }
18 }
19 }
20
21 }
1 package cn.itcast_04;
2
3 /*
4 * 分析:
5 * 學生資源類:Student
6 * 設定學生資料線程類:SetThread(生産者)
7 * 擷取學生資料線程類:GetThread(消費者)
8 * 測試類:StudentDemo
9 *
10 * 問題1:按照思路寫代碼,發現資料每次都是:null---0
11 * 原因:
12 * 我們在每個線程中都建立了新的資源:Student s = new Student();
13 * 而我們要求的是:設定和擷取線程的資源應該是同一個。
14 * 如何解決呢?
15 * 如何把一個資料在多個類中共享使用呢?
16 * 思路1:在外界把這個資料建立出來,通過構造方法傳遞給其他的類。
17 *
18 *
19 * 問題2:為了資料的效果好一些,我加入了循環和判斷,給出不同的值,這個時候産生了新的問題
20 * A:同一個資料出現多次
21 * B:姓名和年齡不比對
22 * 原因:
23 * A:同一個資料出現多次
24 * CPU的一點點時間片的執行權,就足夠你執行很多次。
25 * B:姓名和年齡不比對
26 * 線程運作的随機性
27 * 線程安全問題:
28 * A:是否是多線程環境 是
29 * B:是否有共享資料 是
30 * C:是否有多條語句操作共享資料 是
31 * 解決方案:
32 * 加鎖。
33 * 注意:
34 * A:不同種類的線程都要加鎖。
35 * B:不同種類的線程加的鎖必須是同一把。
36 */
37 public class StudentDemo {
38 public static void main(String[] args) {
39 // 建立資源對象
40 Student s = new Student();
41
42 // 建立設定和擷取對象,并把資源對象作為構造方法的參數進行傳遞
43 SetThread st = new SetThread(s);
44 GetThread gt = new GetThread(s);
45
46 // 建立線程對象
47 Thread t1 = new Thread(st);
48 Thread t2 = new Thread(gt);
49
50 // 啟動線程
51 t1.start();
52 t2.start();
53 }
54 }
C版本代碼如下:
1 package cn.itcast_05;
2
3 public class Student {
4 String name;
5 int age;
6 boolean flag; // flag預設值是false,我們标記為沒有資料;如果flag是true,我們标記為有資料
7 }
1 package cn.itcast_05;
2
3 public class SetThread implements Runnable {
4
5 private int x = 0;
6
7 private Student s;
8
9 // 帶參構造
10 public SetThread(Student s) {
11 this.s = s;
12 }
13
14 @Override
15 public void run() {
16 while (true) {
17 synchronized (s) {
18 // 生産者判斷有沒有資料
19
20 // 有資料就等着消費者先消費資料
21 if (s.flag) {
22 try {
23 s.wait(); // t1等待,wait()方法的特點:1.線程等待并立即釋放鎖。2.線程将來醒過來的時候,是從這裡醒過來的。
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27 }
28
29 // 沒有資料就生産資料
30 if (x % 2 == 0) {
31 s.name = "林青霞";
32 s.age = 27;
33 } else {
34 s.name = "劉意";
35 s.age = 30;
36 }
37 x++;
38
39 // 程式能執行到這裡,說明資料生産出來了
40 s.flag = true; // 修改标記,通知消費者消費資料
41
42 // 喚醒線程(喚醒消費者)
43 s.notify(); // 喚醒t2,喚醒并不表示立馬就可以執行,必須還得搶CPU的執行權
44 }
45 }
46 }
47
48 }
1 package cn.itcast_05;
2
3 public class GetThread implements Runnable {
4
5 private Student s;
6
7 // 帶參構造
8 public GetThread(Student s) {
9 this.s = s;
10 }
11
12 @Override
13 public void run() {
14 while (true) {
15 synchronized (s) {
16 // 消費者判斷有沒有資料
17
18 // 沒有資料就等着生産者先生産資料
19 if (!s.flag) {
20 try {
21 s.wait(); // t2等待,wait()方法的特點:1.線程等待并立即釋放鎖。2.線程将來醒過來的時候,是從這裡醒過來的。
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 }
26
27 // 有資料就消費
28 System.out.println(s.name + "---" + s.age);
29
30 // 程式能執行到這裡,說明資料消費完了
31 s.flag = false; // 修改标記,通知生産者生産資料
32
33 // 喚醒線程(喚醒生産者)
34 s.notify(); // 喚醒t1,喚醒并不表示立馬就可以執行,必須還得搶CPU的執行權
35 }
36 }
37 }
38
39 }
1 package cn.itcast_05;
2
3 /*
4 * 分析:
5 * 學生資源類:Student
6 * 設定學生資料線程類:SetThread(生産者)
7 * 擷取學生資料線程類:GetThread(消費者)
8 * 測試類:StudentDemo
9 *
10 * 問題1:按照思路寫代碼,發現資料每次都是:null---0
11 * 原因:
12 * 我們在每個線程中都建立了新的資源:Student s = new Student();
13 * 而我們要求的是:設定和擷取線程的資源應該是同一個。
14 * 如何解決呢?
15 * 如何把一個資料在多個類中共享使用呢?
16 * 思路1:在外界把這個資料建立出來,通過構造方法傳遞給其他的類。
17 *
18 *
19 * 問題2:為了資料的效果好一些,我加入了循環和判斷,給出不同的值,這個時候産生了新的問題
20 * A:同一個資料出現多次
21 * B:姓名和年齡不比對
22 * 原因:
23 * A:同一個資料出現多次
24 * CPU的一點點時間片的執行權,就足夠你執行很多次。
25 * B:姓名和年齡不比對
26 * 線程運作的随機性
27 * 線程安全問題:
28 * A:是否是多線程環境 是
29 * B:是否有共享資料 是
30 * C:是否有多條語句操作共享資料 是
31 * 解決方案:
32 * 加鎖。
33 * 注意:
34 * A:不同種類的線程都要加鎖。
35 * B:不同種類的線程加的鎖必須是同一把。
36 *
37 *
38 * 問題3:雖然資料安全了,但是呢,一次輸出一大片不好看,我就想依次的一次一個輸出。
39 * 如何解決呢?
40 * 通過Java提供的等待喚醒機制解決。
41 * 等待喚醒:
42 * Object類中提供了三個方法:
43 * public final wait() 等待
44 * public final notify() 喚醒單個線程(用在單個生産線程和單個消費線程)
45 * public final notifyAll() 喚醒所有線程(用在多個生産線程和多個消費線程)
46 * 為什麼這些方法不定義在Thread類中呢?
47 * 這些方法的調用必須通過鎖對象調用,而我們剛才使用的是同步代碼塊,其鎖對象是任意對象。
48 * 是以,這些方法定義在Object類中。
49 */
50 public class StudentDemo {
51 public static void main(String[] args) {
52 // 建立資源對象
53 Student s = new Student();
54
55 // 建立設定和擷取對象,并把資源對象作為構造方法的參數進行傳遞
56 SetThread st = new SetThread(s);
57 GetThread gt = new GetThread(s);
58
59 // 建立線程對象
60 Thread t1 = new Thread(st);
61 Thread t2 = new Thread(gt);
62
63 // 啟動線程
64 t1.start();
65 t2.start();
66 }
67 }
D版本代碼如下:
1 package cn.itcast_07;
2
3 public class Student {
4 private String name;
5 private int age;
6 private boolean flag; // flag預設值是false,我們标記為沒有資料;如果flag是true,我們标記為有資料
7
8 public synchronized void set(String name, int age) {
9 // 生産者判斷有沒有資料
10 // 有資料就等着消費者先消費資料
11 if (this.flag) {
12 try {
13 this.wait();
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 }
18
19 // 沒有資料就生産資料
20 this.name = name;
21 this.age = age;
22
23 // 程式能執行到這裡,說明資料生産出來了
24 this.flag = true; // 修改标記,通知消費者消費資料
25 this.notify(); // 喚醒t2,喚醒并不表示立馬就可以執行,必須還得搶CPU的執行權
26 }
27
28 public synchronized void get() {
29 // 消費者判斷有沒有資料
30 // 沒有資料就等着生産者先生産資料
31 if (!this.flag) {
32 try {
33 this.wait();
34 } catch (InterruptedException e) {
35 e.printStackTrace();
36 }
37 }
38
39 // 有資料就消費
40 System.out.println(this.name + "---" + this.age);
41
42 // 程式能執行到這裡,說明資料消費完了
43 this.flag = false; // 修改标記,通知生産者生産資料
44 this.notify(); // 喚醒t1,喚醒并不表示立馬就可以執行,必須還得搶CPU的執行權
45 }
46 }
1 package cn.itcast_07;
2
3 public class SetThread implements Runnable {
4
5 private int x = 0;
6
7 private Student s;
8
9 // 帶參構造
10 public SetThread(Student s) {
11 this.s = s;
12 }
13
14 @Override
15 public void run() {
16 while (true) {
17 if (x % 2 == 0) {
18 s.set("林青霞", 27);
19 } else {
20 s.set("劉意", 30);
21 }
22 x++;
23 }
24 }
25
26 }
27
1 package cn.itcast_07;
2
3 public class GetThread implements Runnable {
4
5 private Student s;
6
7 // 帶參構造
8 public GetThread(Student s) {
9 this.s = s;
10 }
11
12 @Override
13 public void run() {
14 while (true) {
15 s.get();
16 }
17 }
18
19 }
1 package cn.itcast_07;
2
3 /*
4 * 分析:
5 * 學生資源類:Student
6 * 設定學生資料線程類:SetThread(生産者)
7 * 擷取學生資料線程類:GetThread(消費者)
8 * 測試類:StudentDemo
9 *
10 * 問題1:按照思路寫代碼,發現資料每次都是:null---0
11 * 原因:
12 * 我們在每個線程中都建立了新的資源:Student s = new Student();
13 * 而我們要求的是:設定和擷取線程的資源應該是同一個。
14 * 如何解決呢?
15 * 如何把一個資料在多個類中共享使用呢?
16 * 思路1:在外界把這個資料建立出來,通過構造方法傳遞給其他的類。
17 *
18 *
19 * 問題2:為了資料的效果好一些,我加入了循環和判斷,給出不同的值,這個時候産生了新的問題
20 * A:同一個資料出現多次
21 * B:姓名和年齡不比對
22 * 原因:
23 * A:同一個資料出現多次
24 * CPU的一點點時間片的執行權,就足夠你執行很多次。
25 * B:姓名和年齡不比對
26 * 線程運作的随機性
27 * 線程安全問題:
28 * A:是否是多線程環境 是
29 * B:是否有共享資料 是
30 * C:是否有多條語句操作共享資料 是
31 * 解決方案:
32 * 加鎖。
33 * 注意:
34 * A:不同種類的線程都要加鎖。
35 * B:不同種類的線程加的鎖必須是同一把。
36 *
37 *
38 * 問題3:雖然資料安全了,但是呢,一次輸出一大片不好看,我就想依次的一次一個輸出。
39 * 如何解決呢?
40 * 通過Java提供的等待喚醒機制解決。
41 * 等待喚醒:
42 * Object類中提供了三個方法:
43 * public final wait() 等待
44 * public final notify() 喚醒單個線程
45 * public final notifyAll() 喚醒所有線程
46 * 為什麼這些方法不定義在Thread類中呢?
47 * 這些方法的調用必須通過鎖對象調用,而我們剛才使用的是同步代碼塊,其鎖對象是任意對象。
48 * 是以,這些方法定義在Object類中。
49 *
50 *
51 * 最終版代碼中:
52 * 把Student的成員變量給私有了。
53 * 把設定和擷取的操作給封裝成了功能,并加了同步(同步方法)。
54 * 設定或擷取線程類裡面隻需要調用方法即可。
55 */
56 public class StudentDemo {
57 public static void main(String[] args) {
58 // 建立資源對象
59 Student s = new Student();
60
61 // 建立設定和擷取對象,并把資源對象作為構造方法的參數進行傳遞
62 SetThread st = new SetThread(s);
63 GetThread gt = new GetThread(s);
64
65 // 建立線程對象
66 Thread t1 = new Thread(st);
67 Thread t2 = new Thread(gt);
68
69 // 啟動線程
70 t1.start();
71 t2.start();
72 }
73 }
--------------------------------------
(5)線程的狀态轉換圖及常見的線程執行情況
(6)線程組
Java中使用ThreadGroup類來表示線程組類,它可以對一批線程進行分類管理,Java允許程式直接對線程組進行控制。
預設情況下,所有的線程都屬于主線程組。
1 package cn.itcast_06;
2
3 public class MyRunnable implements Runnable {
4
5 @Override
6 public void run() {
7 for (int x = 0; x < 100; x++) {
8 System.out.println(Thread.currentThread().getName() + ":" + x);
9 }
10 }
11
12 }
MyRunnable.java
1 package cn.itcast_06;
2
3 /*
4 * 線程組: 把多個線程組合到一起。
5 *
6 * 它可以對一批線程進行分類管理,Java允許程式直接對線程組進行控制。
7 */
8 public class ThreadGroupDemo {
9 public static void main(String[] args) {
10 // method1();
11
12 // 我們如何修改線程所在的組呢?
13 // 建立一個線程組,在建立其他線程的時候,把其他線程的組指定為我們自己建立線程組
14 method2();
15 }
16
17 private static void method2() {
18 // 線程組類ThreadGroup的構造方法:public ThreadGroup(String name)
19 ThreadGroup tg = new ThreadGroup("新的線程組");
20 MyRunnable my = new MyRunnable();
21
22 // 線程類Thread的構造方法:public Thread(ThreadGroup group, Runnable target, String name)
23 Thread t1 = new Thread(tg, my, "林青霞");
24 Thread t2 = new Thread(tg, my, "劉意");
25
26 System.out.println(t1.getThreadGroup().getName()); // 新的線程組
27 System.out.println(t2.getThreadGroup().getName()); // 新的線程組
28
29 // 通過線程組組名去設定背景線程,表示該組的線程都是背景線程(守護線程),不用一個一個線程去設定了,多簡潔
30 tg.setDaemon(true);
31 }
32
33 private static void method1() {
34 MyRunnable my = new MyRunnable();
35 Thread t1 = new Thread(my, "林青霞");
36 Thread t2 = new Thread(my, "劉意");
37 // 我不知道上面的線程屬于那個線程組,我想知道,腫麼辦?
38
39 // 線程類Thread裡面的方法:public final ThreadGroup getThreadGroup() 傳回該線程所屬的線程組
40 ThreadGroup tg1 = t1.getThreadGroup();
41 ThreadGroup tg2 = t2.getThreadGroup();
42
43 // 線程組類ThreadGroup裡面的方法:public final String getName() 傳回此線程組的名稱
44 String name1 = tg1.getName();
45 String name2 = tg2.getName();
46 System.out.println(name1); // main
47 System.out.println(name2); // main
48 // 通過結果我們知道了:線程預設情況下屬于main線程組
49
50 // 通過下面的測試,你應該能夠看到,默任情況下,所有的線程都屬于同一個組
51 System.out.println(Thread.currentThread().getThreadGroup().getName()); // 鍊式程式設計
52 }
53 }
ThreadGroupDemo.java
(7)線程池
程式啟動一個新線程成本是比較高的,因為它涉及到要與作業系統進行互動。
而使用線程池可以很好的提高性能,尤其是當程式中要建立大量生存期很短的線程時,更應該考慮使用線程池。
線程池的特點:線程池裡的每一個線程代碼結束後,并不會死亡,而是再次回到線程池中成為空閑狀态,等待下一個對象來使用。
問題:一開始線程池裡面設計多少個線程才合适呢?
答:需要我們進行壓力測試、并發通路測試等。
在JDK5之前,我們必須手動實作自己的線程池,從JDK5開始,Java内置支援線程池。
1 package cn.itcast_08;
2
3 public class MyRunnable implements Runnable {
4
5 @Override
6 public void run() {
7 for (int x = 0; x < 100; x++) {
8 System.out.println(Thread.currentThread().getName() + ":" + x);
9 }
10 }
11
12 }
1 package cn.itcast_08;
2
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5
6 /*
7 * 程式啟動一個新線程成本是比較高的,因為它涉及到要與作業系統進行互動。
8 * 而使用線程池可以很好的提高性能,尤其是當程式中要建立大量生存期很短的線程時,更應該考慮使用線程池。
9 *
10 * 線程池的好處:線程池裡的每一個線程代碼結束後,并不會死亡,而是再次回到線程池中成為空閑狀态,等待下一個對象來使用。
11 *
12 * 在JDK5之前,我們必須手動實作自己的線程池,從JDK5開始,Java内置支援線程池。
13 * JDK5新增了一個Executors工廠類來産生線程池,有如下幾個方法
14 * public static ExecutorService newCachedThreadPool() 建立一個具有緩存功能的線程池
15 * public static ExecutorService newFixedThreadPool(int nThreads) 建立一個可重用的,具有固定線程數的線程池
16 * public static ExecutorService newSingleThreadExecutor() 建立一個隻有單線程的線程池,相當于上個方法的參數是1
17 * 這些方法的傳回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。
18 *
19 * 如何實作線程池呢?
20 * A:建立一個線程池對象,明确線上程池中要建立幾個線程對象。
21 * Executors類的方法:
22 * public static ExecutorService newFixedThreadPool(int nThreads)
23 * 該方法的傳回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。
24 * B:這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程。
25 * 即:做一個類實作Runnable接口。
26 * C:調用如下方法即可
27 * ExecutorService類的方法:
28 * Future<?> submit(Runnable task)
29 * <T> Future<T> submit(Callable<T> task)
30 * D:線程池中的線程使用完畢後,會自動回到線程池中成為空閑狀态,等待下一個對象來使用。
31 * 但是呢,我就要結束線程池,可以嗎?
32 * 可以。
33 */
34 public class ExecutorsDemo {
35 public static void main(String[] args) {
36 // 建立一個線程池對象,明确線上程池中要建立幾個線程對象
37 // Executors類的方法:public static ExecutorService newFixedThreadPool(int nThreads)
38 ExecutorService pool = Executors.newFixedThreadPool(2);
39
40 // 這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程
41 pool.submit(new MyRunnable());
42 pool.submit(new MyRunnable());
43
44 // 結束線程池
45 pool.shutdown();
46 }
47 }
ExecutorsDemo.java
--------------------------------------
(8)多線程實作的第三種方案:依賴于線程池而存在的
如何實作線程池呢?
A:建立一個線程池對象,确定線上程池中要建立幾個線程對象。
Executors類的方法:
public static ExecutorService newFixedThreadPool(int nThreads)
該方法的傳回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。
B:這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程。
即:做一個類實作Runnable接口。
C:調用如下方法即可
ExecutorService類的方法:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
D:線程池中的線程使用完畢後,會自動回到線程池中成為空閑狀态,等待下一個對象來使用。
但是呢,我就要結束線程池,可以嗎?
可以。
好處:
可以有傳回值。
可以抛出異常。
弊端:
代碼比較複雜,而且依賴于線程池而存在的,是以一般不用它。
1 package cn.itcast_09;
2
3 import java.util.concurrent.Callable;
4
5 // Callable:是帶泛型的接口。
6 // 這裡指定的泛型其實是call()方法的傳回值類型。
7 public class MyCallable implements Callable {
8
9 @Override
10 public Object call() throws Exception {
11 for (int x = 0; x < 100; x++) {
12 System.out.println(Thread.currentThread().getName() + ":" + x);
13 }
14 return null;
15 }
16
17 }
MyCallable.java
1 package cn.itcast_09;
2
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5
6 /*
7 * 多線程實作的方式3:依賴于線程池而存在的
8 *
9 * 如何實作線程池呢?
10 * A:建立一個線程池對象,明确線上程池中要建立幾個線程對象。
11 * Executors類的方法:
12 * public static ExecutorService newFixedThreadPool(int nThreads)
13 * 該方法的傳回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。
14 * B:這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程。
15 * 即:做一個類實作Runnable接口。
16 * C:調用如下方法即可
17 * ExecutorService類的方法:
18 * Future<?> submit(Runnable task)
19 * <T> Future<T> submit(Callable<T> task)
20 * D:線程池中的線程使用完畢後,會自動回到線程池中成為空閑狀态,等待下一個對象來使用。
21 * 但是呢,我就要結束線程池,可以嗎?
22 * 可以。
23 */
24 public class CallableDemo {
25 public static void main(String[] args) {
26 // // 建立一個線程池對象,明确線上程池中要建立幾個線程對象
27 ExecutorService pool = Executors.newFixedThreadPool(2);
28
29 // 這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程
30 pool.submit(new MyCallable());
31 pool.submit(new MyCallable());
32
33 // 結束線程池
34 pool.shutdown();
35 }
36 }
CallableDemo.java
線程求和案例:用多線程實作的方式3
1 package cn.itcast_10;
2
3 import java.util.concurrent.Callable;
4
5 /*
6 * 線程求和案例
7 */
8 public class MyCallable implements Callable<Integer> {
9
10 private int number;
11
12 public MyCallable(int number) {
13 this.number = number;
14 }
15
16 @Override
17 public Integer call() throws Exception {
18 int sum = 0;
19 for (int x = 1; x <= number; x++) {
20 sum += x;
21 }
22 return sum;
23 }
24
25 }
1 package cn.itcast_10;
2
3 import java.util.concurrent.ExecutionException;
4 import java.util.concurrent.ExecutorService;
5 import java.util.concurrent.Executors;
6 import java.util.concurrent.Future;
7
8 /*
9 * 多線程實作的方式3:依賴于線程池而存在的
10 *
11 * 如何實作線程池呢?
12 * A:建立一個線程池對象,明确線上程池中要建立幾個線程對象。
13 * Executors類的方法:
14 * public static ExecutorService newFixedThreadPool(int nThreads)
15 * 該方法的傳回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。
16 * B:這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程。
17 * 即:做一個類實作Runnable接口。
18 * C:調用如下方法即可
19 * ExecutorService類的方法:
20 * Future<?> submit(Runnable task)
21 * <T> Future<T> submit(Callable<T> task)
22 * D:線程池中的線程使用完畢後,會自動回到線程池中成為空閑狀态,等待下一個對象來使用。
23 * 但是呢,我就要結束線程池,可以嗎?
24 * 可以。
25 */
26 public class CallableDemo {
27 public static void main(String[] args) throws InterruptedException, ExecutionException {
28 // 建立一個線程池對象,明确線上程池中要建立幾個線程對象
29 ExecutorService pool = Executors.newFixedThreadPool(2);
30
31 // 這種線程池的線程可以執行Runnable對象或者Callable對象代表的線程
32 Future<Integer> f1 = pool.submit(new MyCallable(100));
33 Future<Integer> f2 = pool.submit(new MyCallable(200));
34
35 // Future接口的方法:V get()
36 Integer i1 = f1.get();
37 Integer i2 = f2.get();
38
39 System.out.println(i1);
40 System.out.println(i2);
41
42 // 結束線程池
43 pool.shutdown();
44 }
45 }
--------------------------------------
(9)匿名内部類方式使用多線程
在開發中,為了友善使用線程,需要随手開線程,最簡單的做法就是采用匿名内部類方式使用多線程。
匿名内部類的格式:
new 類名或者接口名() {
重寫方法;
}
本質:是該類的子類對象或者該接口的實作類對象。
new Thread() {代碼...}.start();
new Thread(new Runnable() {代碼...}) {}.start();
示例代碼如下:
1 package cn.itcast_11;
2
3 /*
4 * 匿名内部類的格式:
5 * new 類名或者接口名() {
6 * 重寫方法;
7 * }
8 *
9 * 本質:是該類的子類對象或者該接口的實作類對象。
10 */
11 public class ThreadDemo {
12 public static void main(String[] args) {
13 // 繼承Thread類來實作多線程
14 new Thread() {
15 @Override
16 public void run() {
17 for (int x = 0; x < 100; x++) {
18 System.out.println(Thread.currentThread().getName() + ":" + x);
19 }
20 }
21 }.start();
22
23 // 實作Runnable接口來實作多線程
24 new Thread(new Runnable() {
25 @Override
26 public void run() {
27 for (int x = 0; x < 100; x++) {
28 System.out.println(Thread.currentThread().getName() + ":" + x);
29 }
30 }
31 }) {
32 }.start();
33
34 // 面試題
35 // 到底執行的是Thread類的子類對象的run(),還是執行的是Runnable接口的實作類對象的run()呢? 答:是Thread類的子類對象的run() world
36 new Thread(new Runnable() {
37 @Override
38 public void run() {
39 for (int x = 0; x < 100; x++) {
40 System.out.println("hello" + ":" + x);
41 }
42 }
43 }) {
44 @Override
45 public void run() {
46 for (int x = 0; x < 100; x++) {
47 System.out.println("world" + ":" + x);
48 }
49 }
50 }.start();
51
52 }
53 }
(10)定時器
定時器是一個應用十分廣泛的線程工具,可用于排程多個定時任務以背景線程的方式執行。在Java中,可以通過Timer和TimerTask類來實作定義排程的功能。(在java.util包下)
A:Timer(定時器類)
public Timer()
public void schedule(TimerTask?task, long?delay)
public void schedule(TimerTask task, long delay, long period)
B:TimerTask(任務抽象類)
public abstract void run()
public boolean cancel()
C:在實際開發中
Quartz是一個完全由java編寫的開源排程架構。
D:需求:在指定的時間删除我們的指定目錄(示範:使用項目路徑下的demo)
1 package cn.itcast_12;
2
3 import java.util.Timer;
4 import java.util.TimerTask;
5
6 /*
7 * 定時器:可以讓我們在指定的時間做某件事情,還可以重複的做某件事情。
8 *
9 * 依賴Timer和TimerTask這兩個類:
10 * Timer(定時器類)
11 * public Timer()
12 * public void schedule(TimerTask task, long delay) 在指定延遲後執行指定的任務
13 * public void schedule(TimerTask task, long delay, long period)
14 * public void cancel()
15 *
16 * TimerTask(任務抽象類)
17 */
18 public class TimerDemo {
19 public static void main(String[] args) {
20 // 建立定時器對象
21 Timer t = new Timer();
22
23 // 3秒後執行爆炸任務
24 // t.schedule(new MyTask(), 3000);
25
26 // 3秒後執行爆炸任務,并結束任務
27 t.schedule(new MyTask(t), 3000);
28 }
29 }
30
31 // 做一個任務
32 class MyTask extends TimerTask {
33
34 private Timer t;
35
36 public MyTask(){}
37
38 public MyTask(Timer t){
39 this.t = t;
40 }
41
42 @Override
43 public void run() {
44 System.out.println("beng,爆炸了");
45 t.cancel(); // 結束任務
46 }
47
48 }
TimerDemo.java
1 package cn.itcast_12;
2
3 import java.util.Timer;
4 import java.util.TimerTask;
5
6 /*
7 * 定時器:可以讓我們在指定的時間做某件事情,還可以重複的做某件事情。
8 *
9 * 依賴Timer和TimerTask這兩個類:
10 * Timer(定時器類)
11 * public Timer()
12 * public void schedule(TimerTask task, long delay) 在指定延遲後執行指定的任務
13 * public void schedule(TimerTask task, long delay, long period)
14 * public void cancel()
15 *
16 * TimerTask(任務抽象類)
17 */
18 public class TimerDemo2 {
19 public static void main(String[] args) {
20 // 建立定時器對象
21 Timer t = new Timer();
22
23 // 3秒後執行爆炸任務
24 // t.schedule(new MyTask(), 3000);
25
26 // 3秒後執行爆炸任務第一次,如果不成功,每隔2秒再繼續炸
27 t.schedule(new MyTask2(), 3000, 2000);
28 }
29 }
30
31 // 做一個任務
32 class MyTask2 extends TimerTask {
33
34 @Override
35 public void run() {
36 System.out.println("beng,爆炸了");
37 }
38
39 }
TimerDemo2.java
1 package cn.itcast_12;
2
3 import java.io.File;
4 import java.text.ParseException;
5 import java.text.SimpleDateFormat;
6 import java.util.Date;
7 import java.util.Timer;
8 import java.util.TimerTask;
9
10 /*
11 * 需求:在指定的時間删除我們的指定目錄(示範:使用項目路徑下的demo)
12 */
13 public class TimerTest {
14 public static void main(String[] args) throws ParseException {
15 Timer t = new Timer();
16
17 String s = "2014-11-27 15:45:00";
18 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
19 Date d = sdf.parse(s);
20
21 t.schedule(new DeleteFolder(), d);
22 }
23 }
24
25
26 class DeleteFolder extends TimerTask {
27
28 @Override
29 public void run() {
30 File srcFolder = new File("demo");
31 deleteFolder(srcFolder);
32 }
33
34 // 遞歸删除目錄
35 public void deleteFolder(File srcFolder) {
36 File[] fileArray = srcFolder.listFiles();
37 if (fileArray != null) {
38 for (File file : fileArray) {
39 if (file.isDirectory()) {
40 deleteFolder(file);
41 } else {
42 System.out.println(file.getName() + ":" + file.delete());
43 }
44 }
45 System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
46 }
47 }
48
49 }
在指定的時間删除我們的指定目錄(示範:使用項目路徑下的demo)
--------------------------------------
(11)多線程常見的面試題
1:多線程有幾種實作方案,分别是哪幾種?
兩種。
繼承Thread類
實作Runnable接口
擴充一種:實作Callable接口。這個需要和線程池結合。
--------------------------------------
2:同步有幾種方式,分别是什麼?
兩種。
同步代碼塊
同步方法(靜态同步方法)
--------------------------------------
3:啟動一個線程是run()還是start()?它們的差別?
start();
run():封裝了被線程執行的代碼,直接調用僅僅是普通方法的調用。
start():啟動線程,并由JVM自動調用run()方法。
--------------------------------------
4:sleep()和wait()方法的差別?
sleep():必須指定時間,不釋放鎖。
wait():可以不指定時間,也可以指定時間,并立即釋放鎖。
--------------------------------------
5:為什麼wait()、notify()、notifyAll()等方法都定義在Object類中?
因為這些方法的調用是依賴于鎖對象的,而同步代碼塊的鎖對象是任意對象。
而Object代碼任意的對象,是以,它們定義在這裡面。
--------------------------------------
6:線程的生命周期。
建立-->就緒-->運作-->死亡
建立-->就緒-->運作-->阻塞-->就緒-->運作-->死亡
建議:畫圖解釋。
-----------------------------------------------------------------------------
2:設計模式(了解)
(1)面向對象思想的設計原則的概述
在實際的開發中,我們要想更深入的了解面向對象思想,就必須熟悉前人總結過的面向對象思想的設計原則。
單一職責原則
核心思想:其實就是開發人員經常說的“高内聚”(自己能做的,不麻煩别人)。
也就是說:每個類應該隻有一個職責,對外隻能提供一種功能,而引起類變化的原因應該隻有一個。
在設計模式中,所有的設計模式都遵循這一原則。
開閉原則
核心思想:一個對象對擴充開放,對修改關閉。
其實開閉原則的意思就是:對類的改動是通過增加代碼進行的,而不是修改現有代碼。
也就是說:軟體開發人員一旦寫出了可以運作的代碼,就不應該去改動它,而是要保證它能一直運作下去,如何能夠做到這一點呢?
這就需要借助于抽象和多态,即把可能變化的内容抽象出來,進而使抽象的部分是相對穩定的,而具體的實作則是可以改變和擴充的。
裡氏替換原則
核心思想:在任何父類出現的地方都可以用它的子類來替代。
也就是說:同一個繼承體系中的對象應該有共同的行為特征。
依賴注入原則(Spring架構)
核心思想:要依賴于抽象,不要依賴于具體實作。
也就是說:在應用程式中,所有的類如果使用或依賴于其他的類,則應該依賴這些其他類的抽象類,而不是這些其他類的具體類。
為了實作這一原則,就要求我們在程式設計的時候針對抽象類或者接口程式設計,而不是針對具體實作程式設計。
接口分離原則
核心思想:不應該強迫程式依賴它們不需要使用的方法。
也就是說:一個接口不需要提供太多的行為,一個接口應該隻提供一種對外的功能,不應該把所有的操作都封裝到一個接口中。
迪米特原則
核心思想:一個對象應當對其他對象盡可能少的了解。即“低耦合”(不要牽一發而動全身)
也就是說:降低各個對象之間的耦合,提高系統的可維護性。在子產品之間應該隻通過接口程式設計,而不理會子產品的内部工作原理,它可以使各個子產品耦合度降到最低,促進軟體的複用。
所有的原則都是為了提高程式的可維護性、可擴充性、可複用性。
--------------------------------------
(2)設計模式
A:設計模式的概述(設計模式是經驗的總結)
設計模式(Design pattern)是一套被反複使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人了解、保證代碼可靠性。
設計模式不是一種方法和技術,而是一種思想。
設計模式和具體的語言無關,學習設計模式就是要建立面向對象的思想,盡可能的面向接口程式設計,低耦合,高内聚,使設計的程式可複用。
學習設計模式能夠促進對面向對象思想的了解,反之亦然,它們相輔相成。
B:設計模式的幾個要素
名字:必須有一個簡單、有意義的名字。
問題:描述在何時使用模式。
解決方案:描述設計的組成部分以及如何解決問題。
效果:描述模式的效果以及優缺點。
C:設計模式的分類
建立型模式 對象的建立
結構型模式 對象的組成(結構)
行為型模式 對象的行為
建立型模式:簡單工廠模式、工廠方法模式、抽象工廠模式、建造者模式、原型模式、單例模式。(6個)
結構型模式:外觀模式、擴充卡模式、代理模式、裝飾模式、橋接模式、組合模式、享元模式。(7個)
行為型模式:模版方法模式、觀察者模式、狀态模式、職責鍊模式、指令模式、通路者模式、政策模式、備忘錄模式、疊代器模式、解釋器模式。(10個)
--------------------------------------
(3)常見的設計模式
A:簡單工廠模式(單接口中用的多)
簡單工廠模式的概述
又叫靜态工廠方法模式,它定義一個具體的工廠類負責建立一些類的執行個體。
優點
建立對象的工作其實是比較麻煩的,有了簡單工廠後,用戶端不需要再負責對象的建立,進而明确了各個類的職責。
缺點
這個靜态工廠類負責所有對象的建立,如果有新的對象增加,或者某些對象的建立方式不同,就需要不斷的修改工廠類,不利于後期的維護。
示例代碼如下:
1 package cn.itcast_01;
2
3 public abstract class Animal {
4 public abstract void eat();
5 }
Animal.java
1 package cn.itcast_01;
2
3 public class Dog extends Animal {
4
5 @Override
6 public void eat() {
7 System.out.println("狗吃肉");
8 }
9
10 }
Dog.java
1 package cn.itcast_01;
2
3 public class Cat extends Animal {
4
5 @Override
6 public void eat() {
7 System.out.println("貓吃魚");
8 }
9
10 }
Cat.java
1 package cn.itcast_01;
2
3 public class AnimalFactory {
4
5 private AnimalFactory() {
6 }
7
8 public static Animal createAnimal(String type) {
9 if ("dog".equals(type)) {
10 return new Dog();
11 } else if ("cat".equals(type)) {
12 return new Cat();
13 } else {
14 return null;
15 }
16 }
17
18 /*
19 public static Dog createDog() {
20 return new Dog();
21 }
22
23 public static Cat createCat() {
24 return new Cat();
25 }
26 */
27 }
AnimalFactory.java
1 package cn.itcast_01;
2
3 public class AnimalDemo {
4 public static void main(String[] args) {
5 // 具體類調用
6 Dog d = new Dog();
7 d.eat();
8 Cat c = new Cat();
9 c.eat();
10 System.out.println("------------");
11
12 // 動物工廠有了後,通過工廠造對象
13 // Dog dd = AnimalFactory.createDog();
14 // Cat cc = AnimalFactory.createCat();
15 // dd.eat();
16 // cc.eat();
17 // System.out.println("------------");
18
19 // 工廠改進後
20 // 我要造隻狗
21 Animal a = AnimalFactory.createAnimal("dog");
22 // 記住:用任何一個對象之前先判斷該對象是否為空
23 if (a != null) {
24 a.eat();
25 } else {
26 System.out.println("對不起,暫時不提供這種動物");
27 }
28
29 // 我要造隻貓
30 a = AnimalFactory.createAnimal("cat");
31 // 記住:用任何一個對象之前先判斷該對象是否為空
32 if (a != null) {
33 a.eat();
34 } else {
35 System.out.println("對不起,暫時不提供這種動物");
36 }
37
38 // NullPointerException
39 a = AnimalFactory.createAnimal("pig");
40 // 記住:用任何一個對象之前先判斷該對象是否為空
41 if (a != null) {
42 a.eat();
43 } else {
44 System.out.println("對不起,暫時不提供這種動物");
45 }
46 }
47 }
AnimalDemo.java
B:工廠方法模式(多接口中用的多)
工廠方法模式的概述
工廠方法模式中抽象工廠類負責定義建立對象的接口,具體對象的建立工作由繼承抽象工廠的具體類實作。
優點
用戶端不需要再負責對象的建立,進而明确了各個類的職責,如果有新的對象增加,隻需要增加一個具體的類和具體的工廠類即可,不影響已有的代碼,後期維護容易,增強了系統的擴充性。
缺點
需要額外的編寫代碼,增加了工作量。
示例代碼如下:
1 package cn.itcast_02;
2
3 public abstract class Animal {
4 public abstract void eat();
5 }
1 package cn.itcast_02;
2
3 public class Dog extends Animal {
4
5 @Override
6 public void eat() {
7 System.out.println("狗吃肉");
8 }
9
10 }
1 package cn.itcast_02;
2
3 public class Cat extends Animal {
4
5 @Override
6 public void eat() {
7 System.out.println("貓吃魚");
8 }
9
10 }
1 package cn.itcast_02;
2
3 public interface AnimalFactory {
4 public abstract Animal createAnimal();
5 }
1 package cn.itcast_02;
2
3 public class DogFactory implements AnimalFactory {
4
5 @Override
6 public Animal createAnimal() {
7 return new Dog();
8 }
9
10 }
DogFactory.java
1 package cn.itcast_02;
2
3 public class CatFactory implements AnimalFactory {
4
5 @Override
6 public Animal createAnimal() {
7 return new Cat();
8 }
9
10 }
CatFactory.java
1 package cn.itcast_02;
2
3 public class AnimalDemo {
4 public static void main(String[] args) {
5 // 需求:我要造隻狗
6 AnimalFactory f = new DogFactory();
7 Animal a = f.createAnimal();
8 a.eat();
9 System.out.println("------------");
10
11 // 需求:我要造隻貓
12 f = new CatFactory();
13 a = f.createAnimal();
14 a.eat();
15
16 // 需求:我要造隻...
17 // 隻需要增加一個具體的類和具體的工廠類即可
18 }
19 }
C:單例設計模式(多線程中用的多)
單例設計模式的概述
單例模式就是要確定類在記憶體中隻有一個對象,該執行個體必須自動建立,并且對外提供。
優點
在系統記憶體中隻存在一個對象,是以可以節約系統資源,對于一些需要頻繁建立和銷毀的對象單例模式無疑可以提高系統的性能。
缺點
沒有抽象層,是以擴充很難。職責過重,在一定程式上違背了單一職責。
a:餓漢式:類一加載就建立對象了。
b:懶漢式:用的時候,才去建立對象。
開發中:餓漢式(是不會出現問題的單例模式)
面試中:懶漢式(可能會出現問題的單例模式)
出現什麼問題呢?
A:懶加載思想(延遲加載思想)
B:出現線程安全問題
a:是否是多線程環境 是
b:是否有共享資料 是
c:是否有多條語句操作共享資料 是
示例代碼如下:
餓漢式示例代碼:
1 package cn.itcast_03;
2
3 public class Student {
4
5 // 構造私有,外界就不能造對象了
6 private Student() {
7 }
8
9 // 在成員變量位置自己建立一個對象
10 // 靜态方法隻能通路靜态成員變量,是以成員變量加靜态修飾
11 // 為了不讓外界直接通路修改這個成員變量的值,是以該成員變量加private修飾
12 private static Student s = new Student();
13
14 // 提供公共的通路方式,傳回該對象。為了保證外界能夠直接通路該方法,是以方法加靜态修飾
15 public static Student getStudent() {
16 return s;
17 }
18 }
1 package cn.itcast_03;
2
3 /*
4 * 單例模式:就是要確定類在記憶體中隻有一個對象,該執行個體必須自動建立,并且對外提供。
5 *
6 * 如何保證該類在記憶體中隻有一個對象呢?
7 * A:把構造方法私有,外界就不能造對象了。
8 *
9 * 如何保證該類的對象的自動建立,并且對外提供呢?
10 * A:在成員變量位置自己建立一個對象。靜态方法隻能通路靜态成員變量,是以成員變量加靜态修飾。
11 * 為了不讓外界直接通路修改這個成員變量的值,是以該成員變量加private修飾。
12 * B:通過一個公共的方法提供通路,傳回該對象。為了保證外界能夠直接通路該方法,是以方法加靜态修飾。
13 */
14 public class StudentDemo {
15 public static void main(String[] args) {
16 // Student s1 = new Student();
17 // Student s2 = new Student();
18 // System.out.println(s1 == s2); // false
19
20 // 通過單例如何得到對象呢?先注意一個問題:外界能直接通路修改這個成員變量的值
21 // Student.s = null;
22 //
23 // Student s1 = Student.getStudent();
24 // Student s2 = Student.getStudent();
25 // System.out.println(s1 == s2); // true
26 //
27 // System.out.println(s1); // null
28 // System.out.println(s2); // null
29
30 // 為了不讓外界直接通路修改這個成員變量的值,是以該成員變量加private修飾。
31 // 通過單例如何得到對象呢?
32 Student s1 = Student.getStudent();
33 Student s2 = Student.getStudent();
34 System.out.println(s1 == s2); // true
35
36 System.out.println(s1); // cn.itcast_03.Student@3c679bde
37 System.out.println(s2); // cn.itcast_03.Student@3c679bde
38
39 }
40 }
StudentDemo.java
懶漢式示例代碼:
1 package cn.itcast_03;
2
3 /*
4 * 單例模式:
5 * 餓漢式:類一加載就建立對象了。
6 * 懶漢式:用的時候,才去建立對象。
7 *
8 * 面試題:
9 * 單例模式的思想是什麼? 答:確定類在記憶體中隻有一個對象。
10 * 請寫一個代碼展現。
11 *
12 * 開發中:餓漢式(是不會出現問題的單例模式)
13 * 面試中:懶漢式(可能會出現問題的單例模式)
14 *
15 * 出現什麼問題呢?
16 * A:懶加載思想(延遲加載思想)
17 * B:出現線程安全問題
18 * a:是否是多線程環境 是
19 * b:是否有共享資料 是
20 * c:是否有多條語句操作共享資料 是
21 */
22 public class Teacher {
23 private Teacher() {
24 }
25
26 private static Teacher t = null;
27
28 public static synchronized Teacher getTeacher() {
29 if (t == null) {
30 t = new Teacher();
31 }
32 return t;
33 }
34
35 }
Teacher.java
1 package cn.itcast_03;
2
3 public class TeacherDemo {
4 public static void main(String[] args) {
5 Teacher t1 = Teacher.getTeacher();
6 Teacher t2 = Teacher.getTeacher();
7 System.out.println(t1 == t2); // true
8 System.out.println(t1); // cn.itcast_03.Teacher@3c679bde
9 System.out.println(t2); // cn.itcast_03.Teacher@3c679bde
10 }
11 }
TeacherDemo.java
--------------------------------------
(4)Runtime類的概述和應用
A:Runtime類的概述
每個Java應用程式都有一個Runtime類執行個體,使應用程式能夠與其運作的環境相連接配接。可以通過getRuntime()方法擷取目前運作時對象。
應用程式不能建立自己的Runtime類執行個體。
B:Runtime類使用
public Process exec(String command) 在單獨的程序中執行指定的字元串指令(該方法會抛出異常)
JDK提供的一個單例模式應用的類Runtime類。
該類的對象可以調用dos指令。
示例代碼如下:
1 package cn.itcast_04;
2
3 import java.io.IOException;
4
5 /*
6 * Runtime類:每 Java應用程式都有一個 Runtime類執行個體,使應用程式能夠與其運作的環境相連接配接。可以通過getRuntime()方法擷取目前運作時對象。
7 * public Process exec(String command) 在單獨的程序中執行指定的字元串指令
8 * 該類的對象可以調用dos指令。
9 *
10 * JDK提供的一個單例模式應用的類Runtime類。
11 */
12 public class RuntimeDemo {
13 public static void main(String[] args) throws IOException {
14 Runtime r = Runtime.getRuntime();
15 // r.exec("winmine"); // 打開掃雷遊戲
16 // r.exec("notepad");
17 r.exec("calc");
18 // r.exec("shutdown -s -t 10000"); // 定時10000s秒後關機
19 // r.exec("shutdown -a"); // 取消關機指令
20 }
21 }
22
23 /*
24 * Runtime類的源碼小解析:
25 *
26 * class Runtime {
27 * private Runtime() {}
28 * private static Runtime currentRuntime = new Runtime(); // 單例設計模式之餓漢式
29 * public static Runtime getRuntime() {
30 * return currentRuntime;
31 * }
32 * }
33 */
RuntimeDemo.java
Runtime類的源碼小解析:
class Runtime {
private Runtime() {}
private static Runtime currentRuntime = new Runtime(); // 單例設計模式之餓漢式
public static Runtime getRuntime() {
return currentRuntime;
}
}
=============================================================================
我的GitHub位址:
https://github.com/heizemingjun我的部落格園位址:
http://www.cnblogs.com/chenmingjun我的螞蟻筆記部落格位址:
http://blog.leanote.com/chenmingjunCopyright ©2018 黑澤明軍
【轉載文章務必保留出處和署名,謝謝!】