傳智 劉意 2015年Java基礎視頻-深入淺出精華版 筆記 day24~(2016年4月15日00:39:59)
day24
1.多線程(JDK5之後的Lock鎖的概述和使用)
Lock:
voidlock():獲取鎖。
voidunlock():釋放鎖。
ReentrantLock是Lock的實現類.
Re---entrant---Lock
SellTicket類
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
publicclassSellTicketimplementsRunnable{
//定義票
privateinttickets=100;
//定義鎖對象
privateLocklock=newReentrantLock();
@Override
publicvoidrun(){
while(true){
try{//沒有catch的try……finally塊
//加鎖
lock.lock();
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"張票");
}
}finally{
//釋放鎖
lock.unlock();
}
}
}
}
測試類略,與前一天的一樣。
2.死鎖問題概述和使用
同步的弊端:
A:效率低
B:容易產生死鎖
死鎖:
兩個或兩個以上的線程在爭奪資源的過程中,發生的一種相互等待的現象。
舉例:
中國人,美國人吃飯案例。
正常情況:
中國人:筷子兩支
美國人:刀和叉
現在:
中國人:筷子1支,刀一把(在等待美國人的筷子1支)
美國人:筷子1支,叉一把(在等待中國人的叉一把)
互相等待,但是互相不會放棄手中的筷子和刀(叉)
下面為示例
============================================================
首先,新建一個MyLock 類(作用是創建兩個永不變的鎖,為DieLock類提供鎖,靜態是為了調用方便)
publicclassMyLock{
//創建兩把鎖對象
publicstaticfinalObjectobjA=newObject();
publicstaticfinalObjectobjB=newObject();
//注意,是public而不是private,否則同一個包的不同類調用不了這兩把鎖
}
==========================================================
接著是核心---DieLock類(選其中一種方法:繼承Thread類)
publicclassDieLockextendsThread{
privatebooleanflag;//作用是能在測試類中手動規定是執行if語句的內容還是else語句的內容,
//從而方便做出死鎖的效果(兩個線程,一個設為true,一個設為false,剛好兩條不同的路)
publicDieLock(booleanflag){
this.flag=flag;
}
@Override
publicvoidrun(){
if(flag){
synchronized(MyLock.objA){
System.out.println("ifobjA");
synchronized(MyLock.objB){
System.out.println("ifobjB");
}
}
}else{
synchronized(MyLock.objB){
System.out.println("elseobjB");
synchronized(MyLock.objA){
System.out.println("elseobjA");
}
}
}
}
}
再次提醒一下關鍵代碼
//其實,synchronized嵌套使用一不小心會有死鎖現象
======================================================
最後是測試類
publicclassDieLockDemo{
publicstaticvoidmain(String[]args){
DieLockdl1=newDieLock(true);
DieLockdl2=newDieLock(false);
dl1.start();
dl2.start();
}
}
運行示例
停不下來(其實是卡住了),成功出現了死鎖現象
3.生產者消費者問題描述圖
4.生產者消費者問題代碼1
分析:
資源類:Student
設置學生數據:SetThread(生產者)
獲取學生數據:GetThread(消費者)
測試類:StudentDemo
初步想法
先建Student類
然後SetThread類
然後GetThread類
測試類
如上圖所示,這樣的初始思路是有問題的
問題1:按照思路寫代碼,發現數據每次都是:null---0
原因:我們在每個線程中都創建了新的資源,而我們要求的時候設置和獲取線程的資源應該是同一個
如何實現呢?
在外界把這個數據創建出來,通過構造方法傳遞給其他的類。
改進後代碼如下
===========================
Student類(不變)
publicclassStudent{
Stringname;
intage;
}
==============================
SetThread類
publicclassSetThreadimplementsRunnable{
privateStudents;
publicSetThread(Students){
this.s=s;
}
@Override
publicvoidrun(){
//Students=newStudent();
s.name="林青霞";
s.age=27;
}
}
=================================
GetThread類
publicclassGetThreadimplementsRunnable{
privateStudents;
publicGetThread(Students){
this.s=s;
}
@Override
publicvoidrun(){
//Students=newStudent();
System.out.println(s.name+"---"+s.age);
}
}
====================================
測試類StudentDemo
publicclassStudentDemo{
publicstaticvoidmain(String[]args){
//創建資源
Students=newStudent();
//設置和獲取的類
SetThreadst=newSetThread(s);
GetThreadgt=newGetThread(s);
//線程類
Threadt1=newThread(st);
Threadt2=newThread(gt);
//啟動線程
t1.start();
t2.start();
}
}
5.生產者消費者題代碼2並解決線程安全問題
上一次的代碼有潛在的問題
錯亂的姓名年齡匹配
原因
下面總結問題:
問題1:按照思路寫代碼,發現數據每次都是:null---0
原因:我們在每個線程中都創建了新的資源,而我們要求的時候設置和獲取線程的資源應該是同一個
如何實現呢?
在外界把這個數據創建出來,通過構造方法傳遞給其他的類。
問題2:為了數據的效果好一些,我加入了循環和判斷,給出不同的值,這個時候產生了新的問題
A:同一個數據出現多次
B:姓名和年齡不匹配
原因:
A:同一個數據出現多次
CPU的一點點時間片的執行權,就足夠你執行很多次。
B:姓名和年齡不匹配
線程運行的隨機性
線程安全問題:
A:是否是多線程環境是
B:是否有共享數據是
C:是否有多條語句操作共享數據是
解決方案:
加鎖。
注意:
A:不同種類的線程都要加鎖。
B:不同種類的線程加的鎖必須是同一把。
Student類,測試類一樣,沒變
加鎖要注意的問題
當set和get的鎖都是new Object()的時候
由於獨立都new 了一次,因此鎖是不一樣的
這樣還不能達到效果。鎖必須是同一把才有意義
上圖的s實際上是指通過構造方法傳進來的s對象。
6.生產者消費者之等待喚醒機制思路圖解以及生產者消費者之等待喚醒機制代碼實現,分析
上一節的代碼其實不太好
問題3:雖然數據安全了,但是呢,一次一大片不好看(獲取同一個數據一次而輸出多次,也就是set一次卻get了多次),我就想依次的一次一個輸出(也就是set一次get一次)。
如何實現呢?
通過Java提供的等待喚醒機制解決。
等待喚醒:
Object類中提供了三個方法:
wait():等待
notify():喚醒單個線程
notifyAll():喚醒所有線程
這三個方法都必須在同步代碼塊中執行(例如synchronized塊)
為什麼這些方法不定義在Thread類中呢?
這些方法的調用必須通過鎖對象調用,而我們剛才使用的鎖對象是任意鎖對象。
所以,這些方法必須定義在Object類中。
以下是生產者消費者之等待喚醒機制代碼實現
=====================================
Student類(增加了一個成員變量)
publicclassStudent{
Stringname;
intage;
booleanflag;//默認情況是沒有數據(false),如果是true,說明有數據
}
======================================
SetThread 類
publicclassSetThreadimplementsRunnable{
privateStudents;
privateintx=0;
publicSetThread(Students){
this.s=s;
}
@Override
publicvoidrun(){
while(true){
synchronized(s){
//判斷有沒有
if(s.flag){//如果有數據的話,就wait著
try{
s.wait();//t1等著,釋放鎖
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
if(x%2==0){
s.name="林青霞";
s.age=27;
}else{
s.name="劉意";
s.age=30;
}
x++;//x=1
//修改標記
s.flag=true;
//喚醒線程
s.notify();//喚醒t2,喚醒並不表示你立馬可以執行,必須還得搶CPU的執行權。
}
//t1有,或者t2有
}
}
}
=====================================================
GetThread 類
publicclassGetThreadimplementsRunnable{
privateStudents;
publicGetThread(Students){
this.s=s;
}
@Override
publicvoidrun(){
while(true){
synchronized(s){
if(!s.flag){//如果沒有數據的話,就等著(沒有數據沒法get,只能等著)
try{
s.wait();//t2就等待了。立即釋放鎖。將來醒過來的時候,是從這裡醒過來的時候
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//林青霞---27
//劉意---30
//修改標記
s.flag=false;
//喚醒線程
s.notify();//喚醒t1
}
}
}
}
======================================================
測試類StudentDemo
publicclassStudentDemo{
publicstaticvoidmain(String[]args){
//創建資源
Students=newStudent();
//設置和獲取的類
SetThreadst=newSetThread(s);
GetThreadgt=newGetThread(s);
//線程類
Threadt1=newThread(st);
Threadt2=newThread(gt);
//啟動線程
t1.start();
t2.start();
}
}
=====================================================
運行如下
注意:如果沒有以下代碼
s.flag=true;
s.notify();
的話,會進入死鎖狀態
等待喚醒機制代碼分析
==================================================================
7.線程的狀態轉換圖及常見執行情況
8.線程組的概述和使用
線程組:把多個線程組合到一起。
它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
(MyRunnable類省略)
默認情況下
MyRunnablemy=newMyRunnable();
Threadt1=newThread(my,"林青霞");
Threadt2=newThread(my,"劉意");
//我不知道他們屬於那個線程組,我想知道,怎麼辦
//線程類裡面的方法:publicfinalThreadGroupgetThreadGroup()
ThreadGrouptg1=t1.getThreadGroup();//查看這個線程屬於哪個線程組
ThreadGrouptg2=t2.getThreadGroup();
//線程組裡面的方法:publicfinalStringgetName()
Stringname1=tg1.getName();//get這個線程組的名稱
Stringname2=tg2.getName();
System.out.println(name1);
System.out.println(name2);
//通過結果我們知道了:線程默認情況下屬於main線程組
//通過下面的測試,你應該能夠看到,默任情況下,所有的線程都屬於同一個組
System.out.println(Thread.currentThread().getThreadGroup().getName());
那麼,我們如何修改線程所在的組呢?
//創建一個線程組
//創建其他線程的時候,把其他線程的組指定為我們自己新建線程組
//ThreadGroup(Stringname)
ThreadGrouptg=newThreadGroup("這是一個新的組");
MyRunnablemy=newMyRunnable();
//Thread(ThreadGroupgroup,Runnabletarget,Stringname)
Threadt1=newThread(tg,my,"林青霞");
Threadt2=newThread(tg,my,"劉意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通過組名稱設置後台線程,表示該組的線程都是後台線程
tg.setDaemon(true);
==================================================================
注意:Thread(ThreadGroupgroup,Runnabletarget,Stringname)----三個參數
Threadt1=newThread(tg,my,"林青霞");
Threadt2=newThread(tg,my,"劉意");
Thread三個構造方法
9.生產者消費者之等待喚醒機制代碼優化
也就是,最終版代碼(在Student類中有大改動,然後GetThread類和SetThread類簡潔很多)
=======================================
Student類
publicclassStudent{
privateStringname;
privateintage;
privatebooleanflag;//默認情況是沒有數據,如果是true,說明有數據
publicsynchronizedvoidset(Stringname,intage){
//如果有數據,就等待
if(this.flag){//鎖是this對象
try{
this.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
//設置數據
this.name=name;
this.age=age;
//修改標記
this.flag=true;
this.notify();
}
publicsynchronizedvoidget(){
//如果沒有數據,就等待
if(!this.flag){
try{
this.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
//獲取數據
System.out.println(this.name+"---"+this.age);
//修改標記
this.flag=false;
this.notify();
}
}
========================================
SetThread 類
publicclassSetThreadimplementsRunnable{
privateStudents;
privateintx=0;
publicSetThread(Students){
this.s=s;
}
@Override
publicvoidrun(){
while(true){
if(x%2==0){
s.set("林青霞",27);//這裡直接調用方法而不用考慮同步的問題了----因為Student類搞定一切同步問題
}else{
s.set("劉意",30);//同上
}
x++;
}
}
}
=========================================================
GetThread 類
publicclassGetThreadimplementsRunnable{
privateStudents;
publicGetThread(Students){
this.s=s;
}
@Override
publicvoidrun(){
while(true){
s.get();//直接調用方法,無需考慮同步問題
}
}
}
======================================================
測試類StudentDemo (沒變)
publicclassStudentDemo{
publicstaticvoidmain(String[]args){
//創建資源
Students=newStudent();
//設置和獲取的類
SetThreadst=newSetThread(s);
GetThreadgt=newGetThread(s);
//線程類
Threadt1=newThread(st);
Threadt2=newThread(gt);
//啟動線程
t1.start();
t2.start();
}
}
=========================================================
最終版代碼中:
把Student的成員變量給私有的了。
把設置和獲取的操作給封裝成了功能,並加了同步。
設置或者獲取的線程裡面只需要調用方法即可。
10.線程池的概述和使用
public staticExecutorServicenewCachedThreadPool()
創建一個具有緩存功能的線程池
緩存:百度浏覽過的信息再次訪問
public staticExecutorServicenewFixedThreadPool(intnThreads)
創建一個可重用的,具有固定線程數的線程池
public staticExecutorServicenewSingleThreadExecutor()
創建一個只有單線程的線程池,相當於上個方法的參數是1
==============================================
線程池的好處:線程池裡的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成為空閒狀態,等待下一個對象來使用。
如何實現線程的代碼呢?
A:創建一個線程池對象,控制要創建幾個線程對象。
publicstaticExecutorServicenewFixedThreadPool(intnThreads)
B:這種線程池的線程可以執行:
可以執行Runnable對象或者Callable對象代表的線程
做一個類實現Runnable接口。
C:調用如下方法即可
Futuresubmit(Runnabletask)
Futuresubmit(Callabletask)
D:我就要結束,可以嗎?
可以。
=====================================
代碼區
首先,MyRunnable類
publicclassMyRunnableimplementsRunnable{
@Override
publicvoidrun(){
for(intx=0;x<100;x++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
======================================
ExecutorsDemo 類
publicclassExecutorsDemo{
publicstaticvoidmain(String[]args){
//創建一個線程池對象,控制要創建幾個線程對象。
//publicstaticExecutorServicenewFixedThreadPool(intnThreads)
ExecutorServicepool=Executors.newFixedThreadPool(2);
//可以執行Runnable對象或者Callable對象代表的線程
pool.submit(newMyRunnable());
pool.submit(newMyRunnable());
//結束線程池
pool.shutdown();
}
}
===========================================
如果沒有pool.shutdown(); ----根本停不下來
如果有pool.shutdown();---可以停下來
11.多線程方式3的思路及代碼實現(先忽略泛型)
多線程實現的方式3:
A:創建一個線程池對象,控制要創建幾個線程對象。
publicstaticExecutorServicenewFixedThreadPool(intnThreads)
B:這種線程池的線程可以執行:
可以執行Runnable對象或者Callable對象代表的線程
做一個類實現Runnable接口。
C:調用如下方法即可
Futuresubmit(Runnabletask)
Futuresubmit(Callabletask)
D:我就要結束,可以嗎?
可以。
==============================================
callable---是接口,有返回值,而且依賴於線程池才能使用,一般很少用
Callable:是帶泛型的接口。
這裡指定的泛型其實是call()方法的返回值類型。
MyCallable 類
publicclassMyCallableimplementsCallable{
@Override
publicObjectcall()throwsException{
for(intx=0;x<100;x++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
returnnull;
}
}
================================================
測試類
publicclassCallableDemo{
publicstaticvoidmain(String[]args){
//創建線程池對象
ExecutorServicepool=Executors.newFixedThreadPool(2);
//可以執行Runnable對象或者Callable對象代表的線程
pool.submit(newMyCallable());
pool.submit(newMyCallable());
//結束
pool.shutdown();
}
}
=================================================
12.多線程方式3的求和案例(用上泛型和返回值)
MyCallable 類
新建界面修改一下T
publicclassMyCallableimplementsCallable
{
privateintnumber;//限定范圍的變量
publicMyCallable(intnumber){
this.number=number;
}
@Override
publicIntegercall()throwsException{
intsum=0;
for(intx=1;x<=number;x++){
sum+=x;
}
returnsum;
}
}
====================================
測試類
publicclassCallableDemo{
publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{
//創建線程池對象
ExecutorServicepool=Executors.newFixedThreadPool(2);
//可以執行Runnable對象或者Callable對象代表的線程
Future
f1=pool.submit(newMyCallable(100));
Future
f2=pool.submit(newMyCallable(200));
//Vget()
Integeri1=f1.get();
Integeri2=f2.get();
System.out.println(i1);
System.out.println(i2);
//結束
pool.shutdown();
}
}
=============================================================
13.匿名內部類的方式實現多線程程序
匿名內部類的格式:
new類名或者接口名( ){
重寫方法;
};
本質:是該類或者接口的子類對象。
===============================================
publicclassThreadDemo{
publicstaticvoidmain(String[]args){
//繼承Thread類來實現多線程
newThread(){
publicvoidrun(){
for(intx=0;x<100;x++){
System.out.println(Thread.currentThread().getName()+":"
+x);
}
}
}.start();
//實現Runnable接口來實現多線程
newThread(newRunnable(){
@Override
publicvoidrun(){
for(intx=0;x<100;x++){
System.out.println(Thread.currentThread().getName()+":"
+x);
}
}
}){
}.start();
//更有難度的
newThread(newRunnable(){//實際上是匿名內部類的嵌套??
@Override
publicvoidrun(){
for(intx=0;x<100;x++){
System.out.println("hello"+":"+x);
}
}
}){
publicvoidrun(){
for(intx=0;x<100;x++){
System.out.println("world"+":"+x);
}
}
}.start();
}
}
=======================================================
以下是解析
可以先寫以下框架在擴充代碼
匿名內部類的嵌套???我也不清楚,視頻裡也沒說,不過應該是
子類重寫run方法
以下是一個復雜的,只有面試題才會出的惡性寫法
這個子類對象繼承了Thread類而且重寫父類的run()方法,同時還實現了Runnable接口同時重寫了接口中的run()方法
那麼問題來了,究竟執行哪個呢?答案是"world"這個run方法(用單詞hello和world是為了好區分)
14.定時器的概述和使用
定時器:可以讓我們在指定的時間做某件事情,還可以重復的做某件事情。
依賴Timer和TimerTask這兩個類:
Timer:定時
publicTimer()
publicvoidschedule(TimerTasktask,longdelay)
publicvoidschedule(TimerTasktask,longdelay,longperiod)
publicvoidcancel()
TimerTask:任務
在開發中一般不會用Timer,因為太弱,開發中一般用框架(見下圖)
===================================
publicclassTimerDemo{
publicstaticvoidmain(String[]args){
//創建定時器對象
Timert=newTimer();
//3秒後執行爆炸任務
//t.schedule(newMyTask(),3000);
//結束任務
t.schedule(newMyTask(t),3000);
}
}
//做一個任務
classMyTaskextendsTimerTask{
privateTimert;
publicMyTask(){}
publicMyTask(Timert){
this.t=t;
}
@Override
publicvoidrun(){
System.out.println("beng,爆炸了");
t.cancel();
}
}
=============================================
注意,cancel方法放在run方法執行
如果要不止炸一次怎麼辦??
下面--------
定時任務的多次執行代碼體現
publicclassTimerDemo2{
publicstaticvoidmain(String[]args){
//創建定時器對象
Timert=newTimer();
//3秒後執行爆炸任務第一次,如果不成功,每隔2秒再繼續炸
t.schedule(newMyTask2(),3000,2000);
}
}
//做一個任務
classMyTask2extendsTimerTask{//同一個包下MyTask類只有一個,所以改為MyTask2
@Override
publicvoidrun(){
System.out.println("beng,爆炸了");
}
}
15.案例---定時刪除指定的帶內容目錄
需求:在指定的時間刪除我們的指定目錄(你可以指定c盤,但是我不建議,我使用項目路徑下的demo)
=======================================================
classDeleteFolderextendsTimerTask{
@Override
publicvoidrun(){
FilesrcFolder=newFile("demo");
deleteFolder(srcFolder);
}
//遞歸刪除目錄
publicvoiddeleteFolder(FilesrcFolder){
File[]fileArray=srcFolder.listFiles();
if(fileArray!=null){//使用for循環必須考慮這個問題
for(Filefile:fileArray){
if(file.isDirectory()){
deleteFolder(file);//遞歸
}else{
System.out.println(file.getName()+":"+file.delete());//其實這裡可以直接file.delete();輸出是為了方便查看結果
}
}
System.out.println(srcFolder.getName()+":"+srcFolder.delete());//輸出原因同上
}
}
}
publicclassTimerTest{
publicstaticvoidmain(String[]args)throwsParseException{
Timert=newTimer();
Strings="2014-11-2715:45:00";
SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
Dated=sdf.parse(s);
t.schedule(newDeleteFolder(),d);
}
}
===========================================================
16.多線程常見的面試題
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:線程的生命周期圖
新建--就緒--運行--死亡
新建--就緒--運行--阻塞--就緒--運行--死亡
建議:畫圖解釋。
17.面向對象的常見設計原則概述
面向對象思想設計原則
?在實際的開發中,我們要想更深入的了解面向對象思想,就必須熟悉前人總結過的面向對象的思想的設計原則
?單一職責原則
?開閉原則
?裡氏替換原則
?依賴注入原則
?接口分離原則
?迪米特原則
l單一職責原則
?其實就是開發人員經常說的”高內聚,低耦合”
?也就是說,每個類應該只有一個職責,對外只能提供一種功能,而引起類變化的原因應該只有一個。在設計模式中,所有的設計模式都遵循這一原則。
l開閉原則
?核心思想是:一個對象對擴展開放,對修改關閉。
?其實開閉原則的意思就是:對類的改動是通過增加代碼進行的,而不是修改現有代碼。
?也就是說軟件開發人員一旦寫出了可以運行的代碼,就不應該去改動它,而是要保證它能一直運行下去,如何能夠做到這一點呢?這就需要借助於抽象和多態,即把可能變化的內容抽象出來,從而使抽象的部分是相對穩定的,而具體的實現則是可以改變和擴展的。
l裡氏替換原則
?核心思想:在任何父類出現的地方都可以用它的子類來替代。
?其實就是說:同一個繼承體系中的對象應該有共同的行為特征。
l依賴注入原則
?核心思想:要依賴於抽象,不要依賴於具體實現。
?其實就是說:在應用程序中,所有的類如果使用或依賴於其他的類,則應該依賴這些其他類的抽象類,而不是這些其他類的具體類。為了實現這一原則,就要求我們在編程的時候針對抽象類或者接口編程,而不是針對具體實現編程。
l接口分離原則
?核心思想:不應該強迫程序依賴它們不需要使用的方法。
?其實就是說:一個接口不需要提供太多的行為,一個接口應該只提供一種對外的功能,不應該把所有的操作都封裝到一個接口中。
l迪米特原則
?核心思想:一個對象應當對其他對象盡可能少的了解
?其實就是說:降低各個對象之間的耦合,提高系統的可維護性。在模塊之間應該只通過接口編程,而不理會模塊的內部工作原理,它可以使各個模塊耦合度降到最低,促進軟件的復用
18.設計模式的概述和分類
l設計模式概述
?設計模式(Design pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
?設計模式不是一種方法和技術,而是一種思想
?設計模式和具體的語言無關,學習設計模式就是要建立面向對象的思想,盡可能的面向接口編程,低耦合,高內聚,使設計的程序可復用
?學習設計模式能夠促進對面向對象思想的理解,反之亦然。它們相輔相成
l設計模式的幾個要素
?名字 必須有一個簡單,有意義的名字
?問題 描述在何時使用模式
?解決方案 描述設計的組成部分以及如何解決問題
?效果 描述模式的效果以及優缺點
l設計模式的分類
?創建型模式對象的創建
?結構型模式對象的組成(結構)
?行為型模式對象的行為
常見23個設計模式
創建型模式:簡單工廠模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式,單例模式。(6個)
結構型模式:外觀模式、適配器模式、代理模式、裝飾模式、橋接模式、組合模式、享元模式。(7個)
行為型模式:模版方法模式、觀察者模式、狀態模式、職責鏈模式、命令模式、訪問者模式、策略模式、備忘錄模式、迭代器模式、解釋器模式。(10個)
19.簡單工廠模式概述和使用
貓狗舉例
==================
先不考慮設計模式的話,
先建一個Animal抽象類
publicabstractclassAnimal{
publicabstractvoideat();
}
==================
Dog類
publicclassDogextendsAnimal{
@Override
publicvoideat(){
System.out.println("狗吃肉");
}
}
======================
Cat類
publicclassCatextendsAnimal{
@Override
publicvoideat(){
System.out.println("貓吃魚");
}
}
===========================
測試類
publicclassAnimalDemo{
publicstaticvoidmain(String[]args){
//具體類調用
Dogd=newDog();
d.eat();
Catc=newCat();
c.eat();
System.out.println("------------");
}
=======================================
但是在實際開發中,創建一個對象可能沒那麼容易,所以new一個對象的過程交給一個單獨的類來做
即在同一個包下新建一個工廠類專門生產動物對象
publicclassAnimalFactory{
privateAnimalFactory(){
}
publicstaticDogcreateDog(){
returnnewDog();
}
publicstaticCatcreateCat(){
returnnewCat();
}
}
=========================
測試類測試代碼
//工廠有了後,通過工廠給造
Dogdd=AnimalFactory.createDog();
Catcc=AnimalFactory.createCat();
dd.eat();
cc.eat();
System.out.println("------------");
==================================
對工廠類進行改進
publicclassAnimalFactory{
privateAnimalFactory(){
}
publicstaticAnimalcreateAnimal(Stringtype){
if("dog".equals(type)){
returnnewDog();
}elseif("cat".equals(type)){
returnnewCat();
}else{
returnnull;//不能漏
}
}
}
=================================
測試類測試代碼
//工廠改進後
Animala=AnimalFactory.createAnimal("dog");//只能用Animal接收
a.eat();
a=AnimalFactory.createAnimal("cat");
a.eat();
//NullPointerException
a=AnimalFactory.createAnimal("pig");
if(a!=null){//防止空指針異常
a.eat();
}else{
System.out.println("對不起,暫時不提供這種動物");
}
=======================================
20.工廠方法模式的概述和使用
新建一個包
然後先寫Animal類(抽象類)
publicabstractclassAnimal{
publicabstractvoideat();
}
====================================
然後寫一個總的工廠接口Factory
publicinterfaceFactory{
publicabstractAnimalcreateAnimal();
}
===================================
接著,新建一個空的測試類,假如有需求:我要買只狗
那麼,首先需要有Dog類
publicclassDogextendsAnimal{
@Override
publicvoideat(){
System.out.println("狗吃肉");
}
}
====================================
然後,建一個生產狗的工廠類
publicclassDogFactoryimplementsFactory{
@Override
publicAnimalcreateAnimal(){//多態
returnnewDog();
}
}
=======================================
在測試類中
publicstaticvoidmain(String[]args){
//需求:我要買只狗
Factoryf=newDogFactory();
Animala=f.createAnimal();
a.eat();
System.out.println("-------");
}
========================================
接著,我要買只貓,同理
貓類
publicclassCatextendsAnimal{
@Override
publicvoideat(){
System.out.println("貓吃魚");
}
}
====================================
貓工廠
publicclassCatFactoryimplementsFactory{
@Override
publicAnimalcreateAnimal(){
returnnewCat();
}
}
=================================
測試類
publicclassAnimalDemo{
publicstaticvoidmain(String[]args){
//需求:我要買只狗
Factoryf=newDogFactory();
Animala=f.createAnimal();
a.eat();
System.out.println("-------");
//需求:我要買只貓
f=newCatFactory();
a=f.createAnimal();
a.eat();
}
}
==========================================================
21.單例模式之餓漢式
餓漢的意思:類一加載就造對象
引入:先新建一個空的Student類
測試
單例模式:保證類在內存中只有一個對象。
如何保證類在內存中只有一個對象呢?
A:把構造方法私有
B:在成員位置自己創建一個對象
C:通過一個公共的方法提供訪問
===============================
Student類
publicclassStudent{
//構造私有
privateStudent(){
}
//自己造一個
//靜態方法只能訪問靜態成員變量,加靜態
//為了不讓外界直接訪問修改這個值,加private
privatestaticStudents=newStudent();
//提供公共的訪問方式
//為了保證外界能夠直接使用該方法,加靜態
publicstaticStudentgetStudent(){
returns;
}
}
===============================
測試類
publicclassStudentDemo{
publicstaticvoidmain(String[]args){
//Students1=newStudent();
//Students2=newStudent();
//System.out.println(s1==s2);//false
//通過單例如何得到對象呢?
//Student.s=null;
Students1=Student.getStudent();
Students2=Student.getStudent();
System.out.println(s1==s2);
System.out.println(s1);//null,cn.itcast_03.Student@175078b
System.out.println(s2);//null,cn.itcast_03.Student@175078b
}
}
============================================
注意這行代碼
privatestaticStudents=newStudent();
沒有private會很危險,會導致同一個包的其它類修改s的值
而static則保證個體Student()方法的執行(因為這個方法是靜態的)
成功運行單例模式
21.單例模式之懶漢式
單例模式:
餓漢式:類一加載就創建對象
懶漢式:用的時候,才去創建對象
==============================
先創一個Teacher類
publicclassTeacher{
privateTeacher(){
}
privatestaticTeachert=null;
publicsynchronizedstaticTeachergetTeacher(){
if(t==null){//當t為null時才去創建
t=newTeacher();
}
returnt;
}
}
=========================================
測試類
publicclassTeacherDemo{
publicstaticvoidmain(String[]args){
Teachert1=Teacher.getTeacher();//t為null,因此創建對象
Teachert2=Teacher.getTeacher();//前面已經創建對象了,這一次直接用那個對象
System.out.println(t1==t2);
System.out.println(t1);//cn.itcast_03.Teacher@175078b
System.out.println(t2);//cn.itcast_03.Teacher@175078b
}
}
=========================================
注意:
面試題:單例模式的思想是什麼?請寫一個代碼體現。
開發:餓漢式(是不會出問題的單例模式)
面試:懶漢式(可能會出問題的單例模式)
A:懶加載(延遲加載)
B:線程安全問題
a:是否多線程環境是
b:是否有共享數據是
c:是否有多條語句操作共享數據是
下圖表示Teacher類的方法符合出現線程安全問題的條件
改進關鍵:synchronized
22.單例模式的Java代碼體現Runtime類
=====================================
Runtime源碼
/*
*classRuntime{
*privateRuntime(){}
*privatestaticRuntimecurrentRuntime=newRuntime();
*publicstaticRuntimegetRuntime(){
*returncurrentRuntime;
*}
*}
*/
===========================================
Runtime:每個Java應用程序都有一個Runtime類實例,使應用程序能夠與其運行的環境相連接。
exec(Stringcommand)
=================================================
publicclassRuntimeDemo{
publicstaticvoidmain(String[]args)throwsIOException{
Runtimer=Runtime.getRuntime();
//r.exec("notepad");
//r.exec("calc");
//r.exec("shutdown-s-t10000");
r.exec("shutdown-a");
}
}
================================================
一些解析
一運行
shutdown -a 取消關機
其實簡單來講就是以下作用
day24筆記補充
等待喚醒機制改進該程序,讓數據能夠實現依次的出現
wait()
notify()
notifyAll()(多生產多消費)
特別注意單例模式
單例模式(掌握)
a:餓漢式
b:懶漢式
Runtime
JDK提供的一個單例模式應用的類。
還可以調用dos命令。