Java 學習內容總結

最近對Core Java基礎作了一些學習。有本身的看法,也有別人的總結,供你們參考。java

Java

1 實現多線程的方式有幾種?

其實這個問題並不難,只是在這裏作一個總結。一共有三種。程序員

  • 實現Runnable接口,並實現該接口的run()方法
  • 繼承Thread類,重寫run()方法
  • 實現Callable接口,實現call()方法。

你們可能對前兩種已經很清楚了,重點說下第三種。
Callable接口是屬於Executor框架中的類,Callable 接口與Runnable接口相似,但比後者功能更增強大,主要有三點:算法

  1. Callable能夠在任務結束後提供一個返回值,Runnable沒法提供這個功能;
  2. Callablecall()方法能夠拋出異常,Runnable接口的run()方法不能拋出異常;
  3. 運行Callable能夠獲得一個Future對象,Future對象表示異步計算的結果。它提供了檢查計算是否完成的方法。因爲線程屬於異步計算模型,因此沒法從其餘線程中獲得方法的返回值。在這種狀況下,就可使用Future來監視目標線程調用call()方法的狀況。當調用Futrueget()方法以獲取結果時,當前線程就會阻塞,直到call()方法結束返回結果。

舉個例子,此代碼在JDK 8 下運行,由於使用了lambda表達式:編程

package exam;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableAndFuture {

    public static void main(String[] args) {
    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    // 啓動線程
    Future<String> future = threadPool.submit(() -> "Hello, world");
    
    try {
        System.out.println("waiting thread to finish.");
        System.out.println(future.get()); // 等待線程結束,並獲取返回結果
        
        threadPool.shutdown();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    }
}

2 volatile關鍵字的做用

在Java語言中,有時候爲了提升程序的運行效率,編譯器會作一些優化操做,把常常被訪問的變量緩存起來,程序在讀取這個變量的時候又有可能直接從寄存器中讀取這個值,而不會去內存中讀取。這樣的好處提升了程序的運行效率,但當遇到多線程編程時,變量的值可能被其餘線程改變了,而該緩存的值不會作相應的改變,從而致使應用程序讀取的值可能與實際的變量值不一致。關鍵字volatile正好解決這個問題,被volatile修飾的變量編譯器不會作優化,每次都會從內存讀取。緩存

3 代碼中不一樣屬性和方法的執行順序

常常會遇到一個這樣的代碼,new一個子類,其子類以及父類每一個屬性和方法的執行順序,具體能夠看如下例子:安全

**
 * Java程序初始化工做能夠在許多不一樣的代碼中來完成,它們執行的順序以下:
 * 父類靜態變量
 * 父類靜態代碼塊
 * 子類靜態變量
 * 子類靜態代碼塊
 * 父類非靜態變量
 * 父類非靜態代碼塊
 * 父類構造函數
 * 子類非靜態變量
 * 子類非靜態代碼塊
 * 子類構造函數
 * 
 * 
 * 注意,只有方法具備多態性,屬性則沒有。
 * @author TurtusLi
 *
 */
class BaseI {
    int num = 1;

    public BaseI() {
        this.print();
        num = 2;
    }

    public void print() {
        System.out.println("Base.num = " + num);
    }
}

public class Example1423 extends BaseI {

    int num = 3;

    public Example1423() {
        this.print();

        num = 4;
    }

    // 去掉這個複寫方法,運行看效果
    @Override
    public void print() {
        System.out.println("Sub.num = " + num);
    }

    public static void main(String[] args) {
        BaseI b = new Example1423();
        System.out.println(b.num);
    }

}

4 switch語句支持String類型的實現原理

在Java 7 之後,switch語句能夠用做String類型上。多線程

從本質來說,switch對字符串的支持,其實也是int類型值的匹配。它的實現原理以下:併發

經過對case後面的String對象調用hashCode()方法,獲得一個int類型的Hash值,而後用這個Hash值來惟一標識着這個case框架

那麼當匹配的時候,首先調用這個字符串的hashCode()方法,獲取一個Hash值(int類型),用這個Hash值來匹配全部的case,若是沒有匹配成功,說明不存在;若是匹配成功了,接着會調用字符串的equals()方法進行匹配。異步

由此看出,String變量不能是null;同時,switchcase子句中使用的字符串也不能爲null。

5 多線程同步有幾種實現方法

Java主要提供了三種實現同步機制的方法。

  1. synchronized關鍵字。有兩種用法,能夠是synchronized方法和synchronized代碼塊。
  2. waitnotify方法。
  3. LockLock接口有一個實現類ReentrantLock,也能夠實現多線程的同步。

6 在多線程編程的時候有哪些注意事項

  1. 若是能用volatile代替synchronized,近可能使用volatile。由於被synchronized修飾的方法或代碼塊在同一時間只能容許一個線程訪問,而volatile沒有這個限制,所以使用synchronized會下降併發量。因爲volatile沒法保證原子性操做,所以在多線程的狀況下,只有對變量的操做爲原子操做的狀況下才可使用volatile
  2. 儘量減小synchronized塊內的代碼。
  3. 給每個線程定義一個名字,這樣有利於調試。
  4. 儘可能使用concurrent容器(ConcurrentHashMap)來代替synchronized容器(Hashtable)。
  5. 使用線程池來控制多線程的執行。

7 fail-fast 和fail-safe迭代器的區別是什麼?

他們的主要區別是fail-safe容許在遍歷的過程當中對容器的數據進行修改,而fail-fast則不容許。下面分別介紹這兩種迭代器的工做原理。

fail-fast:直接在容器上進行遍歷,在遍歷的過程當中,一旦發現容器中的數據被修改了(添加元素、刪除或修改元素),就會拋出ConcurrentModificationException異常致使遍歷失敗。常見的使用fail-fast的容器有HashMapArrayList等。

fail-safe:這種遍歷是基於容器的克隆。所以,對容器中內容的修改不影響遍歷。常見使用fail-safe方式的容器有ConcurrentHashMapCopyOnWriteArrayList

8 如何可以使JVM中的虛擬機棧、堆內存和方法區發生內存溢出?

關於JVM的知識,有一本很是好的書籍——周志明《深刻理解Java虛擬機:JVM高級特性與最佳實踐(第2版)》,裏面有很是好的介紹。幾乎能夠說是Java程序員必讀書籍。

虛擬機棧是線程私有的,當建立一個線程時,同時會新建一個虛擬機棧,它描述的是Java方法執行的內存模型。 棧中有一個很是重要的概念——棧幀。棧幀用於保存局部變量表,操做數棧,方法出口等。

其實棧溢出最簡單的方式是無限遞歸。

堆內存是線程共享的,是JVM中內存管理的最大一塊內存,它保存全部實例化的對象。

堆內存溢出最簡單的方式是不停的new對象,GC來不及回收,直到內存所有耗盡。

方法區也是內存共享的。它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

方法區溢出簡單的方式是,調用String類的intern()方法,此方法若是在堆區找不到已經存在的String對象的話,就會往方法區中的常量池放一份,而後返回其引用放在堆區。還有一種辦法是不停地加載類。

9 在 int i =0; i=i++;語句中,i=i++是線程安全的嗎?若是不安全,請說明上面操做在JVM中的執行過程,爲何不安全?說出JDK中哪一個類能達到以上程序的效果,而且是線程安全且高效的,簡述其原理。

語句i=i++的執行過程:先把i的值取出來放到棧頂,能夠理解爲引入了第三方變量k,此時,k的值爲i,而後執行自增操做,因而i的值變爲1,最後執行賦值操做i=k(自增前的值),所以,執行結束後,i的值仍是0。從上面的分析得知,i=i++語句的執行過程是由多個操做組成,它不是原子操做,所以,不是線程安全的。

在Java中,++ii++操做並非線程安全的,在使用的時候,不可避免地會用到synchronized關鍵字。而AtomicInteger是一個提供原子操做的Integer類,它提供了線程安全且高效的原子操做,是線程安全的,其底層的原理是利用處理器的CAS(Compare And Swap,比較與交換,一種有名的無鎖算法)操做來檢測棧中的值是否被其餘線程改變,若是被改變,則CAS操做失敗。這種實現方法在CPU指令級別實現了原子操做,所以,它比使用synchronized來實現同步效率更高。

相關文章
相關標籤/搜索