夜光序言:
一瓢淒冷江湖惹一世歲月浮萍;
一抹禍水硃砂嘆一世浮生飄零;
正文:
以道御術 / 以術識道
1. 多線程的創建方式
(1)、繼承 Thread 類:但 Thread 本質上也是實現了 Runnable 接口的一個實例,它代表一個線程的實例,並且,啓動線程的唯一方法就是通過 Thread 類的 start()實例方法。
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
(2)、實現 Runnable 接口的方式實現多線程,並且實例化 Thread,傳入自己的 Thread 實例,調用 run( )方法
public class MyThread implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
(3)、
使用 ExecutorService、Callable、Future 實現有返回結果的多線程:ExecutorService、Callable、Future
這 個 對 象 實 際 上 都 是 屬 於 Executor 框 架 中 的 功 能 類 。
下面提供了一個完整的有返回結果的多線程測試例子,在 JDK1.5 下驗證過沒問題可以直接使用。代碼如下:
package com.hy.多線程高併發;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
/**
* 有返回值的線程
*/
@SuppressWarnings("unchecked")
public class E {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序開始運行----");
Date date1 = new Date();
int taskSize = 5;
// 創建一個線程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 創建多個有返回值的任務
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 執行任務並獲取 Future 對象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 關閉線程池
pool.shutdown();
// 獲取所有併發任務的運行結果
for (Future f : list) {
// 從 Future 對象上獲取任務的返回值,並輸出到控制檯
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序結束運行----,程序運行時間【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任務啓動");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任務終止");
return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】";
}
}
2. 在 java 中 wait 和 sleep 方法的不同?
最大的不同是在等待時 wait 會釋放鎖,而 sleep 一直持有鎖。wait 通常被用於線程間交互,sleep 通常被用於暫停執行
3. synchronized 和 volatile 關鍵字的作用
volatile 本質是在告訴 jvm 當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;
synchronized 則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
4. 分析線程併發訪問代碼解釋原因
package com.hy.多線程高併發;
public class Counter {
private volatile int count = 0;
public void inc() {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
@Override
public String toString() {
return "[count=" + count + "]";
}
}
package com.hy.多線程高併發;
public class VolatileTest {
public static void main(String[] args) {
final Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.inc();
}
}).start();
}
System.out.println(counter);
}
}
答案是不一定,或者不等於 1000。這是爲什麼呢?
因此結果就不可能等於 1000 了,一般都會小於 1000。
(圖片來自網絡,非本人所繪)
5. 什麼是線程池,如何使用?
在 JDK 的 java.util.concurrent.Executors 中提供了生成多種線程池的靜態方法。
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然後調用他們的 execute 方法即可。
6. 常用的線程池有哪些?
7. 請敘述一下您對線程池的理解?
8. 線程池的啓動策略?
官方對線程池的執行過程描述如下:
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
1、線程池剛創建時,裏面沒有一個線程。任務隊列是作爲參數傳進來的。
2、當調用 execute() 方法添加一個任務時,線程池會做如下判斷:
3、當一個線程完成任務時,它會從隊列中取下一個任務來執行。
4、當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大於corePoolSize,那麼這個線程就被停掉。
所以線程池的所有任務完成後,它最終會收縮到 corePoolSize 的大小。
9. 如何控制某個方法允許併發訪問線程的個數?
package com.hy.多線程高併發;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
/*
* permits the initial number of permits available. This value may be negative,
in which case releases must occur before any acquires will be granted.
fair true if this semaphore will guarantee first-in first-out granting of
permits under contention, else false
*/
static Semaphore semaphore = new Semaphore(5, true);
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
test();
}
}).start();
}
}
public static void test() {
try {
//申請一個請求
semaphore.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "進來了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "走了");
//釋放一個請求
semaphore.release();
}
}
可以使用 Semaphore 控制,第 16 行的構造函數創建了一個 Semaphore 對象,並且初始化了 5 個信號。
10. 三個線程 a、b、c 併發運行,b,c 需要 a 線程的數據怎麼實現
分析過程如下:
解決上面的難題我能想到的兩種方案
package com.hy.多線程高併發;
import java.util.concurrent.Semaphore;
public class ThreadCommunication {
private static int num;
/**
* * 定義一個信號量,該類內部維持了多個線程鎖,可以阻塞多個線程,釋放多個線程,
* 線程的阻塞和釋放是通過 permit 概念來實現的
* * 線程通過 semaphore.acquire()方法獲取 permit,如果當前 semaphore 有 permit 則分配給該線程,
* 如果沒有則阻塞該線程直到 semaphore
* * 調用 release()方法釋放 permit。
* * 構造函數中參數:permit(允許) 個數,
*
*/
private static Semaphore semaphore = new Semaphore(0);
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
//模擬耗時操作之後初始化變量 num
Thread.sleep(1000);
num = 1;
//初始化完參數後釋放兩個 permit
semaphore.release(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
//獲取 permit,如果 semaphore 沒有可用的 permit 則等待,如果有則消耗一個
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "獲取到 num 的值爲:" + num);
}
});
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
try {
//獲取 permit,如果 semaphore 沒有可用的 permit 則等待,如果有則消耗一個
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "獲取到 num 的值爲:" + num);
}
});
//同時開啓 3 個線程
threadA.start();
threadB.start();
threadC.start();
}
}
11. 同一個類中的 2 個方法都加了同步鎖,多個線程能同時訪問同一個類中的這兩個方法嗎?
如下代碼,多個線程不可訪問同一個類中的 2 個加了 Lock 鎖的方法。
package com.hy.多線程高併發;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description:
* 同一個類中的 2 個方法都加了同步鎖,多個線程能同時訪問同一個類中的這兩個方法嗎?
這個問題需要考慮到Lock與synchronized 兩種實現鎖的不同情形。因爲這種情況下使用Lock 和synchronized
會有截然不同的結果。Lock 可以讓等待鎖的線程響應中斷,Lock 獲取鎖,之後需要釋放鎖。如下代碼,多個線程不可
訪問同一個類中的 2 個加了 Lock 鎖的方法
* @return:
* @Author: Hy
* @Date: 2020
*/
public class qq {
private int count = 0; //初始化一個變量
private Lock lock = new ReentrantLock();//設置 lock 鎖
//方法 1
public Runnable run1 = new Runnable() {
public void run() {
lock.lock(); //加鎖
while (count < 1000) {
try {
//打印是否執行該方法
System.out.println(Thread.currentThread().getName() +
" run1: " + count++);
} catch (Exception e) {
e.printStackTrace();
}
}
lock.unlock(); //解鎖
}
};
//方法 2
public Runnable run2 = new Runnable() {
public void run() {
lock.lock(); //上鎖
while (count < 1000) {
try {
System.out.println(Thread.currentThread().getName() +
" run2: " + count++);
} catch (Exception e) {
e.printStackTrace();
}
}
lock.unlock(); //解鎖
}
};
public static void main(String[] args) {
qq t = new qq(); //創建一個對象
new Thread(t.run1).start();//獲取該對象的方法 1
new Thread(t.run2).start();//獲取該對象的方法 2
}
}
而 synchronized 卻不行,使用 synchronized 時,當我們訪問同一個類對象的時候,是同一把鎖,所以可以訪問該對象的其他 synchronized 方法。代碼如下:
package com.hy.多線程高併發;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description:
* 而 synchronized 卻不行,使用 synchronized 時
* 當我們訪問同一個類對象的時候,是同一把鎖,所以可以訪問
* 該對象的其他 synchronized 方法。代碼如下:
* @Param:
* @return:
* @Author: Hy
* @Date: 2020
*/
public class qq1 {
private int count = 0; //初始化一個變量
private Lock lock = new ReentrantLock();//設置 lock 鎖
//方法 1
public Runnable run1 = new Runnable() {
public void run() {
//我們上鎖下
synchronized (this){ //設置關鍵字 synchronized,以當前類爲鎖
while (count < 1000) {
try {
//打印是否執行該方法
System.out.println(Thread.currentThread().getName() +
" run1: " + count++);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
//方法 2
public Runnable run2 = new Runnable() {
public void run() {
synchronized (this){ //設置關鍵字 synchronized,以當前類爲鎖
while (count < 1000) {
try {
System.out.println(Thread.currentThread().getName() +
" run2: " + count++);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
public static void main(String[] args) {
qq1 t = new qq1(); //創建一個對象
new Thread(t.run1).start();//獲取該對象的方法 1
new Thread(t.run2).start();//獲取該對象的方法 2
}
}
12. 什麼情況下導致線程死鎖,遇到線程死鎖該怎麼解決?
11.1 死鎖的定義:所謂死鎖是指多個線程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進。
11.2 死鎖產生的必要條件:
Pn 等待的資源被 P0 佔有,如圖 2-15 所示
11.3 產生死鎖的一個例子
package com.hy.多線程高併發.產生死鎖的例子;
/**
* @Description: /**
* 一個簡單的死鎖類
* 當 DeadLock 類的對象 flag==1 時(td1),先鎖定 o1,睡眠 500 毫秒
* 而 td1 在睡眠的時候另一個 flag==0 的對象(td2)線程啓動,先鎖定 o2,睡眠 500 毫秒
* td1 睡眠結束後需要鎖定 o2 才能繼續執行,而此時 o2 已被 td2 鎖定;
* td2 睡眠結束後需要鎖定 o1 才能繼續執行,而此時 o1 已被 td1 鎖定;
* td1、td2 相互等待,都需要得到對方鎖定的資源才能繼續執行,從而死鎖。
* @return:
* @Author: Hy
* @Date: 2020
*/
public class DeadLock implements Runnable {
public int flag = 1;
//靜態對象是類的所有對象共享的
private static Object o1 = new Object(), o2 = new Object();
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2 都處於可執行狀態,但 JVM 線程調度先執行哪個線程是不確定的。
//td2 的 run()可能在 td1 的 run()之前運行
new Thread(td1).start();
new Thread(td2).start();
}
}
11.4 如何避免死鎖
在有些情況下死鎖是可以避免的。兩種用於避免死鎖的技術:
1)加鎖順序(線程按照一定的順序加鎖)
package com.hy.多線程高併發.如何避免死鎖.加鎖順序;
public class DeadLock {
public int flag = 1;
//靜態對象是類的所有對象共享的
private static Object o1 = new Object(), o2 = new Object();
public void money(int flag) {
this.flag = flag;
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("當前的線程是" +
Thread.currentThread().getName() + " " + "flag 的值" + "1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("當前的線程是" +
Thread.currentThread().getName() + " " + "flag 的值" + "0");
}
}
}
}
public static void main(String[] args) {
final DeadLock td1 = new DeadLock();
final DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2 都處於可執行狀態,但 JVM 線程調度先執行哪個線程是不確定的。
//td2 的 run()可能在 td1 的 run()之前運行
final Thread t1 = new Thread(new Runnable() {
public void run() {
td1.flag = 1;
td1.money(1);
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
try {
//讓 t2 等待 t1 執行完
t1.join();//核心代碼,讓 t1 執行完後 t2 纔會執行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
td2.flag = 0;
td1.money(0);
}
});
t2.start();
}
}
2)加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
package com.hy.多線程高併發.如何避免死鎖.加鎖時限;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadLock {
public int flag = 1;
//靜態對象是類的所有對象共享的
private static Object o1 = new Object(), o2 = new Object();
public void money(int flag) throws InterruptedException {
this.flag = flag;
if (flag == 1) {
synchronized (o1) {
Thread.sleep(500);
synchronized (o2) {
System.out.println("當前的線程是" +
Thread.currentThread().getName() + " " + "flag 的值" + "1");
}
}
}
if (flag == 0) {
synchronized (o2) {
Thread.sleep(500);
synchronized (o1) {
System.out.println("當前的線程是" +
Thread.currentThread().getName() + " " + "flag 的值" + "0");
}
}
}
}
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
final DeadLock td1 = new DeadLock();
final DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2 都處於可執行狀態,但 JVM 線程調度先執行哪個線程是不確定的。
//td2 的 run()可能在 td1 的 run()之前運行
final Thread t1 = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
String tName = Thread.currentThread().getName();
td1.flag = 1;
try {
//獲取不到鎖,就等 5 秒,如果 5 秒後還是獲取不到就返回 false
if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
System.out.println(tName + "獲取到鎖!");
} else {
System.out.println(tName + "獲取不到鎖!");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
try {
td1.money(1);
} catch (Exception e) {
System.out.println(tName + "出錯了!!!");
} finally {
System.out.println("當前的線程是" + Thread.currentThread().getName() + "釋放鎖!!");
lock.unlock();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
public void run() {
String tName = Thread.currentThread().getName();
// TODO Auto-generated method stub
td1.flag = 1;
try {
//獲取不到鎖,就等 5 秒,如果 5 秒後還是獲取不到就返回 false
if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
System.out.println(tName + "獲取到鎖!");
} else {
System.out.println(tName + "獲取不到鎖!");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
try {
td2.money(0);
} catch (Exception e) {
System.out.println(tName + "出錯了!!!");
} finally {
System.out.println("當前的線程是" + Thread.currentThread().getName() + "釋放鎖!!");
lock.unlock();
}
}
});
t2.start();
}
}
13. Java 中多線程間的通信怎麼實現?
線程通信的方式:
1.共享變量
package com.hy.多線程高併發;
public class MySignal {
//共享的變量
private boolean hasDataToProcess = false;
//取值
public boolean getHasDataToProcess() {
return hasDataToProcess;
}
//存值
public void setHasDataToProcess(boolean hasDataToProcess) {
this.hasDataToProcess = hasDataToProcess;
}
public static void main(String[] args) {
//同一個對象
final MySignal my = new MySignal();
//線程 1 設置 hasDataToProcess 值爲 true
final Thread t1 = new Thread(new Runnable() {
public void run() {
my.setHasDataToProcess(true);
}
});
t1.start();
//線程 2 取這個值 hasDataToProcess
Thread t2 = new Thread(new Runnable() {
public void run() {
try {
//等待線程 1 完成然後取值
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
my.getHasDataToProcess();
System.out.println("t1 改變以後的值:" + my.isHasDataToProcess());
}
});
t2.start();
}
//定義一個方法
private boolean isHasDataToProcess() {
return hasDataToProcess;
}
}
2.wait/notify 機制
package com.hy.多線程高併發;
//資源類
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
//生產資源
while (flag) {
try {
//線程等待。消費者消費資源
wait();
} catch (Exception e) {
}
}
this.name = name + "---" + count++;
System.out.println(Thread.currentThread().getName() + "...生產者..." + this.name);
flag = true;
//喚醒等待中的消費者
this.notifyAll();
}
public synchronized void out() {
//消費資源
while (!flag) {
//線程等待,生產者生產資源
try {
wait();
} catch (Exception e) {
}
}
System.out.println(Thread.currentThread().getName() + "...消費者..." + this.name);
flag = false;
//喚醒生產者,生產資源
this.notifyAll();
}
}
//生產者
class Producer implements Runnable {
private Resource res;
Producer(Resource res) {
this.res = res;
}
//生產者生產資源
public void run() {
while (true) {
res.set("商品");
}
}
}
//消費者消費資源
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
res.out();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
14. 線程和進程的區別
進程:具有一定獨立功能的程序關於某個數據集合上的一次運行活動,是操作系統進行資源分配和調度的一個獨立單位。
線程:是進程的一個實體,是 cpu 調度和分派的基本單位,是比進程更小的可以獨立運行的基本單位。
特點:線程的劃分尺度小於進程,這使多線程程序擁有高併發性,進程在運行時各自內存單元相互獨立,線程之間內存共享,這使多線程編程可以擁有更好的性能和用戶體驗
注意:多線程編程對於其它程序是不友好的,佔據大量 cpu 資源。
15. 請說出同步線程及線程調度相關的方法?
而是由 JVM 確定喚醒哪個線程,而且與優先級無關;
16. 啓動一個線程是調用 run()方法還是 start()方法?
十、Java 內部類
1. 靜態嵌套類 (Static Nested Class) 和內部類(Inner Class)的不同?
2. 下面的代碼哪些地方會產生編譯錯誤?
class Outer {
class Inner {}
public static void foo() {
new Inner();
}
public void bar() {
new Inner();
}
public static void main(String[] args) {
new Inner();
}
}
注意:Java 中非靜態內部類對象的創建要依賴其外部類對象
new Outer().new Inner();
JavaSE 高級
一、Java 中的反射
1. 說說你對 Java 中反射的理解
Java 中 的 反 射 首 先 是 能 夠 獲 取 到 Java 中 要 反 射 類 的 字 節 碼 , 獲 取 字 節 碼 有 三 種 方 法
二、Java 中的動態代理
1. 寫一個 ArrayList 的動態代理類
final List<String> list = new ArrayList<String>();
List<String> proxyInstance =
(List<String>)Proxy.newProxyInstance(list.getClass().getClassLoader(),
list.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(list, args);
}
});
proxyInstance.add("你好");
System.out.println(list);
package javase高級.反射;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
List<String> proxyInstance = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(),
list.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(list,args);
}
});
proxyInstance.add("周元");
System.out.println(list);
}
}
2. 動靜態代理的區別,什麼場景使用?
靜態代理通常只代理一個類,動態代理是代理一個接口下的多個實現類。
靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在運行時才知道。
AOP 編程就是基於動態代理實現的,比如著名的 Spring 框架、Hibernate 框架等等都是動態代理的使用例子。
三、Java 中的設計模式&回收機制
1. 你所知道的設計模式有哪些
總體來說設計模式分爲三大類: 【我好像有一個欄目,專門寫了二十三種模式的實際案例】
2. 單例設計模式
最好理解的一種設計模式,分爲懶漢式和餓漢式。
餓漢式:
package javase高級.設計模式.單例模式;
//餓漢式
public class Singleton {
//直接創建對象
private static Singleton instance = new Singleton();
//私有化構造函數
private Singleton(){
}
//返回對象實例
public static Singleton getInstance(){
return instance;
}
}
懶漢式
package javase高級.設計模式.單例模式;
//懶漢式
public class Singleton1 {
//聲明變量
private static volatile Singleton1 singleton1 = null;
//私有構造函數
private Singleton1(){
}
//提供對外的方法,這種更加裝逼。。
public static Singleton1 getInstance(){
if (singleton1 == null){
synchronized (Singleton1.class){
if (singleton1 == null){
singleton1 = new Singleton1();
}
}
}
return singleton1;
}
}
3. 工廠設計模式
package javase高級.設計模式.工廠模式.普通工廠模式;
interface Sender{
public Sender Send();
}
class MailSender implements Sender{
@Override
public Sender Send() {
System.out.println("來自地獄的一封信~~");
return null;
}
}
class SMSender implements Sender{
@Override
public Sender Send() {
System.out.println("堅強的存活下去吧");
return null;
}
}
//我們建一個工廠
class SendFactory{
public Sender produce(String type){
if ("mail".equals(type)){
return new MailSender().Send();
}else if ("sms".equals(type)){
return new SMSender().Send();
}else {
System.out.println("請輸入正確類型");
return null;
}
}
}
public class Test {
public static void main(String[] args) {
SendFactory sendFactory = new SendFactory();
sendFactory.produce("mail");
sendFactory.produce("sms");
}
}
多個工廠方法模式
該模式是對普通工廠方法模式的改進,在普通工廠方法模式中
package javase高級.設計模式.工廠模式.多個工廠方法模式;
interface Sender{
public Sender Send();
}
class MailSender implements Sender{
@Override
public Sender Send() {
System.out.println("花開花落花滿天");
return null;
}
}
class SMSender implements Sender{
@Override
public Sender Send() {
System.out.println("落花無情");
return null;
}
}
class SendFactory{
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SMSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
靜態工廠方法模式,將上面的多個工廠方法模式裏的方法置爲靜態的,不需要創建實例,直接調用即可
package javase高級.設計模式.工廠模式.靜態工廠方法模式;
interface Sender{
public Sender Send();
}
class MailSender implements Sender {
@Override
public Sender Send() {
System.out.println("花開花落花滿天");
return null;
}
}
class SMSender implements Sender {
@Override
public Sender Send() {
System.out.println("落花無情");
return null;
}
}
class SendFactory{
//靜態方法
public static Sender produceMail(){
return new MailSender();
}
//靜態方法
public static Sender produceSms(){
return new SMSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail(); //不需要new,直接調用即可
sender.Send();
}
}
抽象工廠模式
package javase高級.設計模式.工廠模式.最優解抽象工廠模式;
interface Sender{
public void send();
}
interface Provider{
public Sender produce();
}
class MailSender implements Sender{
@Override
public void send() {
System.out.println("上窮碧落下黃泉");
}
}
class SmsSender implements Sender{
@Override
public void send() {
System.out.println("可悲之人,用情之深足以驚天地泣鬼神");
}
}
//工廠一
class SendSmsFactory implements Provider{
@Override
public Sender produce() { //返回一個對象
return new SmsSender();
}
}
//工廠二
class SendMailFactory implements Provider{
@Override
public Sender produce() { //返回一個對象
return new MailSender();
}
}
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender produce = provider.produce();
produce.send();
}
}
4. 建造者模式(Builder)
package javase高級.設計模式.建造者模式;
import java.util.ArrayList;
import java.util.List;
interface Sender{
public void send();
}
interface Provider{
public Sender produce();
}
class MailSender implements Sender{
@Override
public void send() {
System.out.println("上窮碧落下黃泉");
}
}
class SmsSender implements Sender{
@Override
public void send() {
System.out.println("可悲之人,用情之深足以驚天地泣鬼神");
}
}
//其實建造者模式就是前面抽象工廠模式和最後的 Test 結合起來得到的。
class Builder{
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count){
for (int i = 0;i < count;i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for (int i = 0;i < count;i++){
list.add(new SmsSender());
}
}
}
public class TestBuilder {
public static void main(String[] args) {
Builder builder = new Builder();
builder.produceSmsSender(3);
}
}
5. 適配器設計模式
package javase高級.設計模式.適配器設計模式.類的適配器模式;
class Source{
public void method1(){
System.out.println("這是原始的方法~~");
}
}
interface Targetable{
//與原來類中的方法相同
public void method1();
public void method2();
}
class Adapter extends Source implements Targetable{
@Override
public void method2() {
System.out.println("這是目標方法~~");
}
}
public class AdapterTest {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.method1();
adapter.method2();
}
}
對象的適配器模式
package javase高級.設計模式.適配器設計模式.對象的適配器模式;
import com.sun.xml.internal.bind.v2.model.core.Adapter;
/**
* @Description:
* 基本思路和類的適配器模式相同
* 只是將 Adapter 類作修改,這次不繼承 Source 類
* 而是持有 Source 類的實例,以達到解決兼容性的問題。
* @Param:
* @return:
* @Author: Hy
* @Date: 2020
*/
class Source{
public void method1(){
System.out.println("這是原始的方法~~");
}
}
interface Targetable{
//與原來類中的方法相同
public void method1();
public void method2();
}
class Wrapper implements Targetable{
private Source source;
public Wrapper(Source source) {
super();
this.source = source;
}
@Override
public void method1() {
source.method1();
}
@Override
public void method2() {
System.out.println("這是目標方法~~");
}
}
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Wrapper adapter = new Wrapper(source);
adapter.method1();
adapter.method2();
}
}
接口的適配器模式
接口的適配器是這樣的:
該抽象類實現了該接口,實現了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯繫,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行。
6. 裝飾模式(Decorator)
package javase高級.設計模式.裝飾模式;
interface Sourceable{
public void method();
}
class Source implements Sourceable{
@Override
public void method() {
System.out.println("原始方法~~");
}
}
//再寫一個類
class Decorator implements Sourceable{
private Sourceable sourceable;
public Decorator(Sourceable sourceable) {
this.sourceable = sourceable;
}
@Override
public void method() {
System.out.println("裝飾之前~~");
sourceable.method();
System.out.println("裝飾之後~~");
}
}
public class DecoratorTest {
public static void main(String[] args) {
Sourceable sourceable = new Source();
Decorator decorator = new Decorator(sourceable);
decorator.method();
}
}
7. 策略模式(strategy)
策略模式定義了一系列算法,並將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。
需要設計一個接口,爲一系列實現類提供統一的方法,多個實現類實現該接口,設計一個抽象類(可有可無, 屬於輔助類),提供輔助函數。
策略模式的決定權在用戶,系統本身提供不同算法的實現,新增或者刪除算法,對各種算法做封裝。
因此,策略模式多用在算法決策系統中,外部用戶只需要決定用哪個算法即可。
package javase高級.設計模式.策略模式;
interface ICalculator{
public int calculate(String exp);
}
class AbstractCalculator{
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
//定義一個減法
class Minus extends AbstractCalculator implements ICalculator{
@Override
public int calculate(String exp) {
int arrayInt[] = split(exp,"-");
return arrayInt[0] - arrayInt[1];
}
}
//定義一個加法
class Plus extends AbstractCalculator implements ICalculator{
@Override
public int calculate(String exp) {
int arrayInt[] = split(exp,"\\+");
return arrayInt[0] + arrayInt[1];
}
}
public class StrategyTest {
public static void main(String[] args) {
String exp = "2+8";
ICalculator cal = new Plus();
int result = cal.calculate(exp);
System.out.println(result);
}
}
8. 觀察者模式(Observer)
package javase高級.設計模式.觀察者模式;
import java.util.Enumeration;
import java.util.Vector;
interface Observer{
public void update();
}
class Observer1 implements Observer{
@Override
public void update() {
System.out.println("觀察者1,正在思考");
}
}
class Observer2 implements Observer{
@Override
public void update() {
System.out.println("觀察者2,正在思考");
}
}
interface Subject{
//增加觀察者
public void add(Observer observer);
//刪除觀察者
public void del(Observer observer);
//通知所有觀察者
public void notifyObservers();
//自身的操作
public void operation();
}
//我們重寫一下幾個方法
abstract class AbstractSubject implements Subject{
//這裏我們使用線程安全的一個集合,存放一下
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration<Observer> enumo = vector.elements();
while (enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
}
class MySubject extends AbstractSubject{
@Override
public void operation() {
System.out.println("更新一下" +
":-------");
notifyObservers();
}
}
public class ObserverTest {
public static void main(String[] args) {
MySubject mySubject = new MySubject();
mySubject.add(new Observer1());
mySubject.add(new Observer2());
mySubject.operation();
}
}
9. JVM 垃圾回收機制和常見算法
常用的搜索算法如下:
1)引用計數器算法(廢棄)
2)根搜索算法(使用)
GC Roots 對象包括:
通過上面的算法搜索到無用對象之後,就是回收過程,回收算法如下:
1)標記—清除算法(Mark-Sweep)(DVM 使用的算法)
而且清除後回產生大量的不連續空間,這樣當程序需要分配大內存對象時,可能無法找到足夠的連續空間
2)複製算法(Copying)
複製算法是把內存分成大小相等的兩塊,每次使用其中一塊
3)標記—整理算法(Mark-Compact)
4)分代收集(Generational Collection)
10. 談談 JVM 的內存結構和內存分配
a) Java 內存模型
Java 虛擬機將其管轄的內存大致分三個邏輯部分:方法區(Method Area)、Java 棧和 Java 堆。
b) java 內存分配