夜光:Java成神之路(十四)擅長的語言

夜光序言:

 

一瓢淒冷江湖惹一世歲月浮萍;
一抹禍水硃砂嘆一世浮生飄零;

 

 

 

 
 
正文:
 
                                              以道御術 / 以術識道



1. 多線程的創建方式

 

(1)、繼承 Thread 類:但 Thread 本質上也是實現了 Runnable 接口的一個實例,它代表一個線程的實例,並且,啓動線程的唯一方法就是通過 Thread 類的 start()實例方法。

 
 
start()方法是一個 native 方法,它將啓動一個新線程,並執行 run()方法。
 
 
這種方式實現多線程很簡單,通過自己的類直接 extend Thread,並複寫 run()方法,就可以啓動新線程並執行自己定義的 run()方法。
 
例如:繼承 Thread 類實現多線程,並在合適的地方啓動線程
 
    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 框 架 中 的 功 能 類 。

 
 
想 要 詳 細 了 解 Executor 框 架 的 可 以 訪 問 http://www.javaeye.com/topic/366591 ,這裏面對該框架做了很詳細的解釋。
 
 
 
返回結果的線程是在 JDK1.5 中引入的新特徵,確實很實用,有了這種特徵我就不需要再爲了得到返回值而大費周折了,而且即便實現了也可能漏洞百出。
 
 
可返回值的任務必須實現 Callable 接口,類似的,無返回值的任務必須 Runnable 接口。
 
 
執行 Callable 任務後,可以獲取一個 Future 的對象,在該對象上調用 get 就可以獲取到 Callable 任務返回的 Object 了,再結合線程池接口 ExecutorService 就可以實現傳說中有返回結果的多線程了。
 
 
 

下面提供了一個完整的有返回結果的多線程測試例子,在 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 修飾之後,那麼就具備了兩層語義:
 
1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
 
2)禁止進行指令重排序。

 

volatile 本質是在告訴 jvm 當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;

 
 

synchronized 則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。

 
 
1.volatile 僅能使用在變量級別;
 
synchronized 則可以使用在變量、方法、和類級別的
 
2.volatile 僅能實現變量的修改可見性,並不能保證原子性;
 
synchronized 則可以保證變量的修改可見性和原子性
 
3.volatile 不會造成線程的阻塞;

 

synchronized 可能會造成線程的阻塞。
 
4.volatile 標記的變量不會被編譯器優化;
 
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。這是爲什麼呢?

 
 
在 java 的內存模型中每一個線程運行時都有一個線程棧,線程棧保存了線程運行時候變量值信息。
 
 
當線程訪問某一個對象時候值的時候,首先通過對象的引用找到對應在堆內存的變量的值,然後把堆內存變量的具體值 load 到線程
 
 
本地內存中,建立一個變量副本,之後線程就不再和對象在堆內存變量值有任何關係,而是直接修改副本變量的值,在修改完之後的某一個時刻(線程退出之前),自動把線程變量副本的值回寫到對象在堆中變量。
 
 
這樣在堆中的對象的值就產生變化了。
 
 
也就是說上面主函數中開啓了 1000 個子線程,每個線程都有一個變量副本,每個線程修改變量只是臨時修改了自己的副本,當線程結束時再將修改的值寫入在主內存中,這樣就出現了線程安全問題。

 

 

因此結果就不可能等於 1000 了,一般都會小於 1000。

 
 
上面的解釋用一張圖表示如下:

(圖片來自網絡,非本人所繪)


 

5. 什麼是線程池,如何使用?

 

線程池就是事先將多個線程對象放到一個容器中,當使用的時候就不用 new 線程而是直接去池中拿線程即可,節省了開闢子線程的時間,提高的代碼執行效率。
 
 

在 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. 常用的線程池有哪些?

 

newSingleThreadExecutor:創建一個單線程的線程池,此線程池保證所有任務的執行順序按照任務的提交順序執行。
 
 
newFixedThreadPool:創建固定大小的線程池,每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。
 
 
newCachedThreadPool:創建一個可緩存的線程池,此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說 JVM)能夠創建的最大線程大小。
 
 
 
newScheduledThreadPool:創建一個大小無限的線程池,此線程池支持定時以及週期性執行任務的需求。
 
 
newSingleThreadExecutor:創建一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。

 


 

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() 方法添加一個任務時,線程池會做如下判斷:

 
 
a. 如果正在運行的線程數量小於 corePoolSize,那麼馬上創建線程運行這個任務;
 
b. 如果正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列。
 
c. 如果這時候隊列滿了,而且正在運行的線程數量小於 maximumPoolSize,那麼還是要創建線程運行這個任務;
 
d. 如果隊列滿了,而且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常,告訴調用者“我不能再接受任務了”。
 

 

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 個信號。

 
 
這樣的效果是控件 test 方法最多只能有 5 個線程併發訪問,對於 5 個線程時就排隊等待,走一個來一下。第 33 行,請求一個信號(消費一個信號),如果信號被用完了則等待,第 45 行釋放一個信號,釋放的信號新的線程就可以使用了。

 


 

10. 三個線程 a、b、c 併發運行,b,c 需要 a 線程的數據怎麼實現

根據問題的描述,我將問題用以下代碼演示,ThreadA、ThreadB、ThreadC,ThreadA 用於初始化數據 num,只有當 num 初始化完成之後再讓 ThreadB 和 ThreadC 獲取到初始化後的變量 num。
 
 

 

分析過程如下:

 
 
 
考慮到多線程的不確定性,因此我們不能確保 ThreadA 就一定先於 ThreadB 和 ThreadC 前執行,就算 ThreadA 先執行了,我們也無法保證 ThreadA 什麼時候才能將變量 num 給初始化完成。
 
因此我們必須讓 ThreadB 和 ThreadC 去等待 ThreadA 完成任何後發出的消息。
 
 
現在需要解決兩個難題,一是讓 ThreadB 和 ThreadC 等待 ThreadA 先執行完,二是 ThreadA 執行完之後給ThreadB 和 ThreadC 發送消息。

 

 

解決上面的難題我能想到的兩種方案

 
一是使用純 Java API 的 Semaphore 類來控制線程的等待和釋放
 
 
二是使用 Android 提供的 Handler 消息機制。
 

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 個方法都加了同步鎖,多個線程能同時訪問同一個類中的這兩個方法嗎?

 
這個問題需要考慮到Lock與synchronized 兩種實現鎖的不同情形。因爲這種情況下使用Lock 和synchronized 會有截然不同的結果。
 
Lock 可以讓等待鎖的線程響應中斷,Lock 獲取鎖,之後需要釋放鎖。
 
 

如下代碼,多個線程不可訪問同一個類中的 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 死鎖產生的必要條件:

 
互斥條件:線程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某 資源僅爲一個線程所佔有。此時若有其他線程請求該資源,則請求線程只能等待。
 
 
不剝奪條件:線程所獲得的資源在未使用完畢之前,不能被其他線程強行奪走,即只能由獲得該資源的線程自己來釋放(只能是主動釋放)。
 
 
請求和保持條件:線程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他線程佔有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
 
 
循環等待條件:存在一種線程資源的循環等待鏈,鏈中每一個線程已獲得的資源同時被鏈中下一個線程所請求。
 
 
即存在一個處於等待狀態的線程集合{Pl, P2, ..., pn},其中 Pi 等待的資源被 P(i+1)佔有(i=0, 1, ..., n-1),
 

 

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.共享變量

 
線程間通信可以通過發送信號,發送信號的一個簡單方式是在共享對象的變量裏設置信號值。
 
 
線程 A 在一個同步塊裏設置 boolean 型成員變量 hasDataToProcess 爲 true,線程 B 也在同步塊裏讀取 hasDataToProcess 這個成員變量。
 
 
這個簡單的例子使用了一個持有信號的對象,並提供了 set 和 get 方法:
 
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. 請說出同步線程及線程調度相關的方法?

 

wait():使一個線程處於等待(阻塞)狀態,並且釋放所持有的對象的鎖;
 
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理 InterruptedException 異常;
 
notify():喚醒一個處於等待狀態的線程,當然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,

 

而是由 JVM 確定喚醒哪個線程,而且與優先級無關;

 
notityAll():喚醒所有處於等待狀態的線程,該方法並不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態;
 
 
注意:java 5 通過 Lock 接口提供了顯示的鎖機制,Lock 接口中定義了加鎖(lock()方法)和解鎖(unLock()方法),增強了多線程編程的靈活性及對線程的協調
 
 

 


16. 啓動一個線程是調用 run()方法還是 start()方法?

 

啓動一個線程是調用 start()方法,使線程所代表的虛擬處理機處於可運行狀態,這意味着它可以由 JVM 調度並執行,這並不意味着線程就會立即運行。
 
run()方法是線程啓動後要進行回調(callback)的方法。
 

 

十、Java 內部類

1. 靜態嵌套類 (Static Nested Class) 和內部類(Inner Class)的不同?

 

靜態嵌套類:Static Nested Class 是被聲明爲靜態(static)的內部類,它可以不依賴於外部類實例被實例化。
 
內部類:需要在外部類實例化後才能實例化,其語法看起來挺詭異的。

 


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 中非靜態內部類對象的創建要依賴其外部類對象

 
 
上面的試題中 foo 和 main 方法都是靜態方法
 
 
靜態方法中沒有 this,也就是說沒有所謂的外部類對象,因此無法創建內部類對象,如果要在靜態方法中創建內部類對象,可以這樣做
 
new Outer().new Inner();

 

 

JavaSE 高級


 

一、Java 中的反射

 

1. 說說你對 Java 中反射的理解

Java 中 的 反 射 首 先 是 能 夠 獲 取 到 Java 中 要 反 射 類 的 字 節 碼 , 獲 取 字 節 碼 有 三 種 方 法

 
1.Class.forName(className)
 
2.類名.class
 
3.this.getClass()。
 
 
然後將字節碼中的方法,變量,構造函數等映射成相應的 Method、Filed、Constructor 等類,這些類提供了豐富的方法可以被我們所使用。

二、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. 動靜態代理的區別,什麼場景使用?

靜態代理通常只代理一個類,動態代理是代理一個接口下的多個實現類。

 

靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在運行時才知道。

 
動態代理是實現 JDK 裏的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你的業務類必須要實現接口,通過 Proxy 裏的 newProxyInstance 得到代理對象。
 
 
還有一種動態代理 CGLIB,代理的是類,不需要業務類繼承接口,通過派生的子類來實現代理。通過在運行時,動態修改字節碼達到修改類的目的。

 

AOP 編程就是基於動態代理實現的,比如著名的 Spring 框架、Hibernate 框架等等都是動態代理的使用例子。

 


三、Java 中的設計模式&回收機制

 

1. 你所知道的設計模式有哪些

Java 中一般認爲有 23 種設計模式,我們不需要所有的都會,但是其中常用的幾種設計模式應該去掌握。
 
 
下面列出了所有的設計模式。需要掌握的設計模式我單獨列出來了,當然能掌握的越多越好。

 

總體來說設計模式分爲三大類: 【我好像有一個欄目,專門寫了二十三種模式的實際案例】

 
創建型模式,共五種:工廠方法模式抽象工廠模式單例模式建造者模式、原型模式。
 
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式
 
行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
 
 

 

 

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)

 
工廠類模式提供的是創建單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來創建複合對象,
 
 
所謂複合對象就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最後的 Test 結合起來得到的

 

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();

    }
}

對象的適配器模式

 
基本思路和類的適配器模式相同,只是將 Adapter 類作修改
 
 
這次不繼承 Source 類,而是持有 Source 類的實例,以達到解決兼容性的問題。

 

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)

 
 
觀察者模式很好理解,類似於郵件訂閱和 RSS 訂閱,當我們瀏覽一些博客或 wiki 時,經常會看到 RSS 圖標,就這的意思是,當你訂閱了該文章,如果後續有更新,會及時通知你。
 
 
其實,簡單來講就一句話:當一個對象變化時,其它依賴該對象的對象都會收到通知,並且隨着變化
 
對象之間是一種一對多的關係。

 

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 垃圾回收機制和常見算法

 

理論上來講 Sun 公司只定義了垃圾回收機制規則而不侷限於其實現算法,因此不同廠商生產的虛擬機採用的算法也不盡相同。
GC(Garbage Collector)在回收對象前首先必須發現那些無用的對象,如何去發現定位這些無用的對象?
 
 

常用的搜索算法如下:

1)引用計數器算法(廢棄)

 
引用計數器算法是給每個對象設置一個計數器,當有地方引用這個對象的時候,計數器+1,當引用失效的時候,計數器-1,當計數器爲 0 的時候,JVM 就認爲對象不再被使用,是“垃圾”了。
 
 
引用計數器實現簡單,效率高;
 
 
但是不能解決循環引用問問題(A 對象引用 B 對象,B 對象又引用 A 對象,但是A,B 對象已不被任何其他對象引用),同時每次計數器的增加和減少都帶來了很多額外的開銷,所以在 JDK1.1 之後,這個算法已經不再使用了。

 

 

2)根搜索算法(使用)

 
根搜索算法是通過一些“GC Roots”對象作爲起點,從這些節點開始往下搜索,搜索通過的路徑成爲引用鏈 (Reference Chain),當一個對象沒有被 GC Roots 的引用鏈連接的時候,說明這個對象是不可用的。
 
 
 
 

 


GC Roots 對象包括:

 
a) 虛擬機棧(棧幀中的本地變量表)中的引用的對象。
 
b) 方法區域中的類靜態屬性引用的對象。
 
c) 方法區域中常量引用的對象。
 
d) 本地方法棧中 JNI(Native 方法)的引用的對象。
 

 


 

通過上面的算法搜索到無用對象之後,就是回收過程,回收算法如下:

1)標記—清除算法(Mark-Sweep)(DVM 使用的算法)

 
標記—清除算法包括兩個階段:“標記”和“清除”。在標記階段,確定所有要回收的對象,並做標記。
 
 
清除階段緊隨標記階段,將標記階段確定不可用的對象清除。
 
 
標記—清除算法是基礎的收集算法,標記和清除階段的效率不高,
 

 

而且清除後回產生大量的不連續空間,這樣當程序需要分配大內存對象時,可能無法找到足夠的連續空間

 


2)複製算法(Copying)

複製算法是把內存分成大小相等的兩塊,每次使用其中一塊

 
當垃圾回收的時候,把存活的對象複製到另一塊上,然後把這塊內存整個清理掉。
 
 
複製算法實現簡單,運行效率高,但是由於每次只能使用其中的一半,造成內存的利用率不高。
 
 
現在的 JVM 用複製方法收集新生代,由於新生代中大部分對象(98%)都是朝生夕死的,所以兩塊內存的比例不是 1:1(大概是 8:1)。

 

3)標記—整理算法(Mark-Compact)

 

 
標記—整理算法和標記—清除算法一樣,但是標記—整理算法不是把存活對象複製到另一塊內存,而是把存活對象往內存的一端移動,然後直接回收邊界以外的內存。
 
 
 
標記—整理算法提高了內存的利用率,並且它適合在收集對象存活時間較長的老年代。

 

4)分代收集(Generational Collection)

 
分代收集是根據對象的存活時間把內存分爲新生代和老年代,根據各個代對象的存活特點,每個代採用不同的垃圾回收算法。
 
 
新生代採用複製算法,老年代採用標記—整理算法。
 
 
垃圾算法的實現涉及大量的程序細節,而且不同的虛擬機平臺實現的方法也各不相同。

 


10. 談談 JVM 的內存結構和內存分配

 
 

a) Java 內存模型

Java 虛擬機將其管轄的內存大致分三個邏輯部分:方法區(Method Area)、Java 棧和 Java 堆。

 
1、方法區是靜態分配的,編譯器將變量綁定在某個存儲位置上,而且這些綁定不會在運行時改變。
 
常數池,源代碼中的命名常量、String 常量和 static 變量保存在方法區。
 
 
 
2、Java Stack 是一個邏輯概念,特點是後進先出。一個棧的空間可能是連續的,也可能是不連續的。
 
 
最典型的 Stack 應用是方法的調用,Java 虛擬機每調用一次方法就創建一個方法幀(frame),退出該方法則對應的 方法幀被彈出(pop)。棧中存儲的數據也是運行時確定的。
 
 
3、Java 堆分配(heap allocation)意味着以隨意的順序,在運行時進行存儲空間分配和收回的內存管理模型。
 
 
堆中存儲的數據常常是大小、數量和生命期在編譯時無法確定的。Java 對象的內存總是在 heap 中分配。
 
 
我們每天都在寫代碼,每天都在使用 JVM 的內存。
 

b) java 內存分配

 
 
1、基礎數據類型直接在棧空間分配;
 
2、方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收;
 
3、引用數據類型,需要用 new 來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量;
 
4、方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完後從棧空間回收;
 
5、局部變量 new 出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待 GC 回收;
 
 
6、方法調用時傳入的實際參數,先在棧空間分配,在方法調用完成後從棧空間釋放;
 
7、字符串常量在 DATA 區域分配 ,this 在堆空間分配;
 
8、數組既在棧空間分配數組名稱, 又在堆空間分配數組實際的大小

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章