Java 核心技術(shù)(卷一)筆記

1.Java是一種強(qiáng)類型語言

  • 在JAVA中,所有的數(shù)據(jù)類型所占的字節(jié)數(shù)量與平臺無關(guān)。
  • Java沒有任何無符號(unsigned)形式的int、long、short或byte類型。

2.基本類型(8種)

  • 整型 - (4種)

    類型 大小 取值范圍
    int 4字節(jié) -2 147 483 648 ~ 2 147 483 647(正好超過20億)
    short 2字節(jié) -32 768 ~ 32 767
    long 8字節(jié) -9 223 372036 854 775 808 ~ 9 223 372 036 854 775 807
    byte 1字節(jié) -128 ~ 127
    • L或l : 長整型(40000000L)
    • 0x或0X : 十六進(jìn)制數(shù)(0xCAFE)
    • 0 : 八進(jìn)制數(shù)(010 -> 十進(jìn)制數(shù)8)
    • 0b或0B : 二進(jìn)制數(shù)(0b1001 -> 十進(jìn)制數(shù)9)*Jdk7+
  • 浮點(diǎn)類型 - (2種)

    類型 大小 取值范圍
    float 4字節(jié) 大約±3.402 823 47E+38F(有效位數(shù)為6 ~ 7位)
    double 8字節(jié) 大約±1.797 693 134 862 315 70E+308(有效位數(shù)為15位)
    • 絕大部分應(yīng)用程序都采用double類型。
  • 表示Unicode編碼的字符單元的字符類型(char) - (1種)

    • 原本用于表示單個字符。
    • 有些Unicode字符可以用一個char值描述,另外一些Unicode字符則需要兩個char值。
    • 在Java中,char類型描述了UTF-16編碼中的一個代碼單元。強(qiáng)烈建議不要在程序中使用char類型,除非確實需要處理UTF-16代碼單元
  • 表示真值(boolean) - (1種)
    true 、 false

3.變量

  • 常量
    利用關(guān)鍵字final指示常量
  • 構(gòu)建字符串
    • StringBuilder : 效率高,線程不安全
    • StringBuffer : 效率低,線程安全
  • switch語句
    case標(biāo)簽可以是:
    - 類型為char、byte、short或int的常量表達(dá)式。
    - 枚舉常量。
    - 字符串字面量(jdk7+)
  • 大數(shù)值(類)
    • BigInteger: 實現(xiàn)任意精度的整數(shù)運(yùn)算
    • BigDecimal: 實現(xiàn)了任意精度的浮點(diǎn)數(shù)運(yùn)算
  • 數(shù)組拷貝
    Arrays.copyOf()
  • 數(shù)組排序
    Arrays,sort()

4.對象與類

類設(shè)計技巧

  • 一定要保證數(shù)據(jù)私有
  • 一定要對數(shù)據(jù)初始化
  • 不要在類中使用過多的基本類型
  • 不是所有的域都需要獨(dú)立的域訪問器和域更改器
  • 將職責(zé)過多的類進(jìn)行分解
  • 類名和方法名都要 能夠體現(xiàn)它們的職責(zé)
  • 優(yōu)先使用不可變的類

5.繼承

  • 阻止繼承:final類和方法

    • 修飾類:阻止定義類的子類
    • 修飾方法: 阻止子類重寫方法
  • 強(qiáng)制類型轉(zhuǎn)換

    • 只能在繼承層次內(nèi)進(jìn)行類型轉(zhuǎn)換
    • 在將超類轉(zhuǎn)換成子類之前,應(yīng)該使用instanceof進(jìn)行檢查
  • 抽象類

    • 使用abstract關(guān)鍵字
  • 可見性控制修飾符

    • private : 僅對本類可見
    • public : 對所有類可見
    • protected : 對本包和所有子類可見
    • 默認(rèn) : 對本包可見
  • 參數(shù)可變的方法
    eg: method(Object ...args)

  • 枚舉類

    /**
    * @author chenchao
    * @Date 2018/6/20  下午6:37
    */
    public enum EnumTest {
    
        SMALL("s"),MEDIUM("m");
    
        EnumTest(String s) {
        }
    }
    
    //System.out.println(EnumTest.MEDIUM.toString());
    
  • 反射

    • 功能
      • 在運(yùn)行時分析類的能力
      • 在運(yùn)行時查看對象,例如,編寫一個toString方法供所有類使用
      • 實現(xiàn)通用的數(shù)組操作代碼
      • 利用Method對象,這個對象很像C++中的函數(shù)
    • class類
      • 調(diào)用靜態(tài)方法forName獲得類名對應(yīng)Class對象
        String classname = "java.util.Random";
        Class cl = Class.forName(classname);
        Object o = Class.forName(classname).newInstance();
        
    • 利用反射分析類的能力
      • Field、MethodConstructor: 域、方法、構(gòu)造器
      • setAccessible(boolean flag)
        • 為反射對象設(shè)置可訪問標(biāo)志
      • 方法調(diào)用
        public Object invoke(Object implicitParametter,Object[] explicitParamenters)
        
  • 繼承的設(shè)計技巧

    • 將公共操作和域放在超類
    • 不要使用受保護(hù)的域
    • 使用繼承實現(xiàn)‘is-a’關(guān)系
    • 除非所有繼承的方法都有意義,否則不要使用繼承
    • 在覆蓋方法時,不要改變預(yù)期的行為
    • 使用多態(tài),而非類型信息
    • 不要過多地使用反射

6.接口、lambda表達(dá)式于內(nèi)部類

  • 接口(interface)

  • lambda表達(dá)式

    (arg0, arg1 ...) -> {
        //do soming
    }
    
  • 函數(shù)式接口(functional interface)

    • 對于只有一個抽象方法的接口,需要這種接口的對象時,就可以提供一個lambda表達(dá)式。這種接口稱為函數(shù)式接口。

    eg:

    Arrays.sort(words, 
        (first, second) -> first.length() - second.length()
        );
    
    //lambda表達(dá)式可以轉(zhuǎn)換為接口
    
  • 方法引用

    有時,可能會已經(jīng)有現(xiàn)成的方法可以完成你想要傳遞到其他代碼的某個動作。
    eg:

      //Timer t = new Timer(1000, event -> System.out.priontln(event));
      Timer t = new Timer(1000, System.out::println);
    
    • 表達(dá)式System.out.println是一個方法引用(method regerence), 它等價于lambda表達(dá)式:x -> System.out.println(x)

    • 要用::操作符分隔方法名于與對象或類名。主要有3種情況:

      • object::instanceMethod
      • Class::staticMethod
      • Class::instanceMethod
        eg:
      System.out::println 等價于 x -> System.out.println(x)
      math::pow 等價于 (x,y) -> Math.pow(x,y)
      
      //第三種, 第一個參數(shù)會成為方法的目標(biāo)。
      String::compareToIgnoreCase 等同于 (x,y) -> x.compareToIgnoreCase(y)
      

      類似于lambda表達(dá)式,方法引用不能獨(dú)立存在,總會轉(zhuǎn)換為函數(shù)式接口的實例

    • 構(gòu)造器引用

      構(gòu)造器引用與方法引用很類似,只不過方法名為new。
      Person::new 是Person構(gòu)造器的一個引用。
      java ArrayList<String> names = .....; Stream<Person> stream = names.stream().map(Person::new); List<Person> people = stream.collect(Collectors.toLiost());
      - 內(nèi)部類
      - new內(nèi)部類示例: OuterClass.InnerClass object = OuterClass.new IbnnerClass();
      被編譯成的文件:Outer$Inner.class
      - 內(nèi)部類可以訪問外部類的私有數(shù)據(jù)。
      - 只有內(nèi)部類可以是私有類,而常規(guī)類只可以具有包可見性,或者共有可見性。
      - 內(nèi)部類中聲明的所有靜態(tài)域都必須是final
      - 內(nèi)部類不能有static方法

      1. 局部內(nèi)部類

        image

        • 局部內(nèi)部類不能用public或private訪問說明符進(jìn)行聲明,它的作用域被限制在了聲明它的塊中。
        • 與其他內(nèi)部類相比,局部類還有一個優(yōu)點(diǎn),不僅能夠訪問包含它們的外部類,還可以訪問局部變量,不過那些局部變量必須實事上為final。
      2. 匿名內(nèi)部類

        image

        • ActionListener是接口。
        Person queen = new Person("Mary");
        //a Person object
        Person count = new Person("Dracula"){....};
        // an object of an inner class extending Person
        

        *** 現(xiàn)在有更好的辦法使用Lambda表達(dá)式

      3. 靜態(tài)內(nèi)部類

        有時候,使用內(nèi)部類只是為了把一個類隱藏在另外一個類內(nèi)部,并不需要內(nèi)部類引用外圍類對象。為此,可以將內(nèi)部類聲明為static,以便取消產(chǎn)生的引用。
        ```java
        ArrayAlg.Pair p = ArrayAlg.minmax(d);

        //類定義
        class ArrayAlg{
        public static class Pair{
        ....
        }

         //用來返回兩個參數(shù)
         public static Pair minmax(double[] d){
             ...
             return new Pair(min, max);
         }
        

        }

        - 在內(nèi)部類不需要訪問外部類對象時,應(yīng)該使用靜態(tài)內(nèi)部類。
        - 與常規(guī)內(nèi)部類不同,靜態(tài)內(nèi)部類可以有靜態(tài)域和方法
        - 聲明在接口中的內(nèi)部類,自動成為static和public類。      
        
    • 代理

      利用代理可以在運(yùn)行時創(chuàng)建一個實現(xiàn)了一組給定接口的新類。(程序設(shè)計人員使用機(jī)會較少)

      • (未完成)

7. 異常、斷言和日志

異常處理的任務(wù)就是將控制權(quán)從錯誤產(chǎn)生的地方轉(zhuǎn)移給能夠處理這些情況的錯誤處理器。

  • 異常分類

    image

    所有的異常都是由Throwable繼承而來。但在下一層分解為兩個分支:Error 和 Ecxeption。

    • Error 類層次結(jié)構(gòu)描述了Java運(yùn)行時系統(tǒng)的內(nèi)部錯誤和資源耗盡錯誤。
    • Exception層次結(jié)構(gòu),在設(shè)計Java程序時需要關(guān)注
      • RuntimeException:由程序錯誤導(dǎo)致的異常。
      • 其他異常:程序本身沒有問題,但由于像I/O錯誤這類問題導(dǎo)致的異常。
  • 聲明受查異常:

    在下面四中情況下應(yīng)該拋出異常

    • 調(diào)用一個拋出受檢查異常的方法,例如,F(xiàn)ileInputStream構(gòu)造器。

    • 程序運(yùn)行過程中發(fā)現(xiàn)錯誤,并且利用throw語句拋出一個受檢查異常。

    • 程序出現(xiàn)錯誤,例如a[-1]=0會拋出一個ArrayIndexOutOfBoundsException這樣的非受查異常。

    • Java虛擬機(jī)和運(yùn)行時庫出現(xiàn)內(nèi)部錯誤。

      出現(xiàn)前兩種之一,就必須告訴調(diào)用這個方法的程序員有可能拋出異常。

      聲明方法可能拋出的異常:

      Class MyAnimation{
          ...
              public Image loadImage(String s) throws FileNotFoundException, EOFException{
              
          }
      }
      

      拋出異常:

      throw new EOFException();
      
  • 創(chuàng)建異常類

    //定義一個派生于Exception的類,或者派生于Exception子類的類
    
    //例如,定義一個派生于IOException的類
    Class FileFormatException extends IOException{
        public FileFormatException(){}
        public FileFormatException(String gripe){
            super(gripe);
        }
    }
    
    // 習(xí)慣上,定義的類應(yīng)該包含兩個構(gòu)造器,一個是默認(rèn)的構(gòu)造器;
    // 另一個是帶有詳細(xì)信息描述的構(gòu)造器。
    
    
  • 捕獲異常

    try{
        ....
    }catch(Exception e){
        handler for this type
    }
    

    如果try語句塊中的任何代碼拋出一個在catch子句中說明的異常類:

    • 程序?qū)⑻^try語句塊的其余代碼
    • 程序?qū)?zhí)行catch子句中的處理代碼
  • finally子句:

    不管是否有異常被捕獲,finally子句中的代碼都被執(zhí)行。

    • try語句可以只有finally語句,而沒有catch子句。
  • 帶資源的try語句(Java SE 7)

    try-with-resources

    try(Resource res = ...){
        work with res
    }
    

    try塊退出時,會自動調(diào)用res.close()。

  • 使用異常機(jī)制的技巧:

    • 異常處理不能代替簡單的測試

      捕獲異常耗時,應(yīng)該只在異常情況下使用異常機(jī)制。

    • 不要過分地細(xì)化異常

      代碼量急劇膨脹

    • 利用異常層次結(jié)構(gòu)(應(yīng)該尋找更加適合的異常子類,或自己創(chuàng)建異常類)

    • 不要壓制異常

    • 在檢測錯誤時,“苛刻”比放任更好

    • 不要羞于傳遞異常

    (早拋出,晚捕獲)

8. 泛型程序設(shè)計

  • 泛型類
    public class Pair<T>{
    
        private T first;
    
        public T getFirst(){
            return this.first;
        }
    } 
    
  • 泛型方法
    class ArrayAlg{
        public static <T> T getMiddle(T...a){
            return a[a.length/2]
        }
    }
    
    //調(diào)用
    String moddle = ArrayAlg.<String>getModdle("John", "H", "public");
    //編譯器可推斷出
    String moddle = ArrayAlg.getModdle("John", "H", "public");
    
  • 類型變量的限定
    //限制T實現(xiàn)了Comparable接口
    public static <T extends Comparable> T min(T[] a){}
    
    //<T extends Comparable & Serializable>
    
  • 通配符概念
    • Pair<? extends Employee> : 表示任何泛型Pair類型,它的類型參數(shù)是Employee的子類,如Pair<Manager>
  • 通配符的超類型限定

    ? super Manager
    這個通配符限制為Manager的所有超類型(包含Manager)

  • 反射和泛型
    • 泛型類的實例,得不到太多信息,因為他們會被擦除。
    • 可以獲得泛型類的信息
      • 泛型Class類

        //反射獲取泛型類型
        public class TestServiceBase<T> {
        
            private Class<T> clazz;
        
            public void print(){
                //當(dāng)前對象的直接超類的 Type
                Type genericSuperclass = getClass().getGenericSuperclass();
                System.out.println(genericSuperclass.getTypeName());
                if(genericSuperclass instanceof ParameterizedType){
                    //參數(shù)化類型
                    ParameterizedType parameterizedType= (ParameterizedType) genericSuperclass;
                    //返回表示此類型實際類型參數(shù)的 Type 對象的數(shù)組
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                    this.clazz= (Class<T>)actualTypeArguments[0];
                }else{
                    this.clazz= (Class<T>)genericSuperclass;
                }
        
                System.out.println(this.clazz.getName());
            }
        }
        

9. 集合

  • Java庫中的具體集合

    image

    image

  • Vector所有的方法為同步方法(安全,耗時),ArrayList方法不是同步的。

  • 迭代器

    Set<String> set = new HashSet<>();
    
    for (int i = 0; i < 100; i++) {
        set.add(i + "");
    }
    System.out.println(set.size()); //100
    
    Iterator<String> iterator = set.iterator();
    while (iterator.hasNext()){
        if(Integer.parseInt(iterator.next()) % 2 == 0){
            iterator.remove(); //刪除上一次next的數(shù)據(jù)
            iterator.remove(); //Error 
            
            /*
            *iterator.remove();
            *iterator.next()
            *iterator.remove(); //OK!
            */
        }
    }
    
    System.out.println(set.size()); //50
    
  • 散列集

    鏈表和數(shù)組可以按照人們的意愿排列元素的次序。但是,如果想要查看某個指定的元素,卻又忘記了它的位置,就需要訪問所有元素,直到找到為止

    • 散列表

      image
      image

      例如,某個對象的散列碼是76268,并且有128個桶,對象應(yīng)該存在第108號桶(76268除以128余108)。

  • 樹集

    TreeSet 類與散列集十分相似,不過,它比散列集有所改進(jìn)。樹集是一個有序集合。

    • 按照任意順序插入,遍歷時,每個值將按照排序后的順序呈現(xiàn)。

    • 排序是用樹結(jié)構(gòu)完成的(目前使用的是紅黑樹)。

    • 將一個元素加到樹中要比加到散列中慢。

      image
    • 要使用樹集,必須能夠比較元素(元素實現(xiàn)Comparable接口, 或者構(gòu)造集時必須提供一個Comparator)

  • 隊列與雙端隊列

    Java SE 6加入Deque接口, 并由ArrayDeque 和 LinkedList實現(xiàn), 這兩個類都提供了雙端隊列。

  • 優(yōu)先級隊列

    PriorityQueue

    • 元素按照任意順序插入,按照排序的順序檢索?!?gt;也就是說,無論何時調(diào)用remove方法,總會獲得當(dāng)前優(yōu)先級隊列中最小的元素。

    • 然而,優(yōu)先級隊列并沒有對所有元素進(jìn)行排序。

    • 優(yōu)先級隊列使用了一個優(yōu)雅且高效的數(shù)據(jù)結(jié)構(gòu),稱為堆(heap)。堆時一個可以自我調(diào)整的二叉樹,對樹執(zhí)行添加(add)和刪除(remove)操作,可以讓最小的元素移動到根,而不必話費(fèi)時間對元素進(jìn)行排序。

    • 與TreeSet中的迭代不同,這里的迭代并不是按照元素的排列順序訪問的。而刪除卻總是刪除掉剩余元素中優(yōu)先級數(shù)最小的那個元素。

  • 映射

    映射(map)用來存放鍵/值對。

    • Java類庫為映射提供了兩個通用的實現(xiàn):HashMap 和 TreeMap。這兩個類都實現(xiàn)了Map接口。

      • 散列映射對鍵進(jìn)行散列。

      • 樹映射用鍵的整體順序?qū)υ剡M(jìn)行排序,并將其組織成搜索樹。

      • 散列稍微快一些

  • 鏈接散列集與映射

    LinkedHashSetLinkedHashMap類用來記住插入元素項的順序。

    image
    Map<String, Employee> staff = new LinkedHashMap<>();
    staff.put("144-25-5464", new Employee("Amy Lee"));
    staff.put("144-25-5466", new Employee("Harry"));
    ......
    //然后,staff.keySet().iterator()以下面的次序枚舉鍵:
    //144-25-5464
    //144-25-5466
    // .....
    
  • 枚舉集與映射

    EnumSet 是一個枚舉類型元素集的高效實現(xiàn)。由于枚舉類型只有有限個實例,所以EnumSet內(nèi)部用位序列實現(xiàn)。如果對應(yīng)的值在集中,則相應(yīng)的位被置為1。

    • 使用靜態(tài)工廠方法構(gòu)造這個集:

      image
EnumMap是一個鍵類型為枚舉類型的映射。它可以直接且高效地用一個數(shù)值組實現(xiàn)。在使用時,需要在構(gòu)造器中指定鍵類型:

    EnumMap<Weekday, Employee> personInCharge = new EnumMap<>(Weekday.class);
  • 標(biāo)識散列映射

    IdentutyHashMap有特殊的作用。在這個類中,鍵的散列值不是用hashCode函數(shù)計算的,而是用System.identityHashCode方法計算的。(也就是說,不同的鍵對象,即使內(nèi)容相同,也被視為是不同的對象。)

  • 視圖與包裝器

    • 輕量級集合包裝器

      • Arrays類的靜態(tài)方法asList將返回一個爆炸股了普通Java數(shù)組的List包裝器。

        Card[] cardDeck = new Card[52];
        ...
        List<Card> cardList = Arrays.asList(cardDeck);
        

        發(fā)那會的對象不是 ArrayList 。他是一個視圖對象,帶有訪問底層數(shù)組的方法。

- 子范圍

  ```java
  List group2 = staff.subList(10, 20);
  
  group2.clear();//staff reduction
  //現(xiàn)在,元素自動地從staff列表中清除了,并且group2為空。
  ```

  

- 同步視圖

  如果由多個線程訪問集合,就必須確保集不會被意外地破壞。

  類庫的設(shè)計者使用視圖機(jī)制來確保常規(guī)集合的線程安全,而不是實現(xiàn)線程安全的集合類。

  eg:  `Collections類的靜態(tài)synchronizedMap`

  ```java
  Map<String, Employee> mapl = Collections.synchronizedMap(new HashMap<String, Employee>());
  ```

  <font color="red">通常,視圖有一些局限性,即可能只可以讀、無法改變大小、只支持刪除而不支持插入,這些與映射的鍵視圖情況相同。如果試圖進(jìn)行不恰當(dāng)?shù)牟僮?,受限制的視圖就會拋出一個UnsupportedOperationException。</font>
  • 算法

    • 排序與混排

      • 排序

        List<String> staff = new LinkedList<>();
        Collections.sort(staff);
        //或者
        staff.sort(Comparator.comparingDouble(Employee::getSalary));//使用List接口的sort方法,并傳入一個Comparator對象。
        staff.sort(Comparator.comparingDouble(Employee::getSalary).reversed());//按工資逆序排序
        

        排序算法實現(xiàn):Java語言直接將所有元素轉(zhuǎn)入一個數(shù)組中,對數(shù)組進(jìn)行排序,然后再將排序后的序列復(fù)制回列表。

      • 混排

        ArrayList<Card> cards = ...;
        Collections.shuffle(cards);
        

        如果提供的列表沒有實現(xiàn)RandomAccess接口,shuffle方法將元素復(fù)制到數(shù)組中,然后打亂數(shù)組元素的順序,最后再將打亂順序后的元素復(fù)制回列表。

    • 二分查找

      Collections類的binarySearch方法實現(xiàn)了這個算法。注意,集合必須是排好序的。

  • 簡單算法

    • Collections.max()

    • Collections.replaceAll()

    • Collections.removeIf() (Java SE 8)

    • List.replaceAll() (Java SE 8)

      eg:

      words.removeIf(w -> w.length() <= 3);
      words.replaceAll(String::toLowerCase);
      

      。。。

  • 批操作

    coll1.removeAll(coll2); //從coll1中刪除coll2中出現(xiàn)的所有元素
    
    coll1.retainAll(coll2); //從coll中刪除所有未在coll2中出現(xiàn)的元素。(例如,生成交集)
    
  • 集合與數(shù)組轉(zhuǎn)換

    • 數(shù)組 ---> 集合: Arrays.asList包裝器

    • 集合 ——> 數(shù)組:

      復(fù)雜一點(diǎn)

      Integer[] nums = new Integer[1024];
      List<Integer> list = Arrays.asList(nums);
      
      
      Integer[] integers = list.toArray(new Integer[0]); //提供一個所需類型而且長度為0的數(shù)組,返回的數(shù)組就會創(chuàng)建為相同的數(shù)組類型。
      System.out.println(integers.length);//1024
      
      list.toArray(); //返回Object[]數(shù)組
      
      //如果愿意,可以創(chuàng)造一個指定大小的數(shù)組:
      list.toArray(new Integer[list.size()]); //這樣不會創(chuàng)建新數(shù)組
      
  • 遺留集合

    image
    • Hashtable

      與Vector類的方法一樣。Hashtable的方法也是同步的。

      應(yīng)該使用 HashMap. 需要并發(fā)訪問,則使用ConcurrentHashMap

    • 枚舉 Enumneration

    • 屬性映射 Properties

    • 棧 Satck

    • 位集 BitSet

10. 部署Java 應(yīng)用程序

  1. 創(chuàng)建jar文件

    jar cvf JARFILENAME file1 file2 ....

    image
  2. 清單文件

    除了類文件、圖像和其他資源外,每個JAR文件還包含一個用于描述歸檔特征的清單文件(manifest)

    清單文件被命名為MANIFEST,位于JAR文件的一個特殊MATA-INF子目錄中。

11.并發(fā)

  • 中斷線程

    沒有可以強(qiáng)制線程終止的方法。然而, interrupt方法可以用來請求終止線程。對一個線程調(diào)用interrupt方法時,線程的中斷狀態(tài)將被置位。

    但是,如果線程被阻塞,就無法檢測中斷狀態(tài)。這是產(chǎn)生InterruptedException異常的地方。

    • interrput() : 向線程發(fā)送中斷請求。
    • Interrupted: 測試當(dāng)前線程是否被中斷,并清除線程的中斷狀態(tài)(副作用)。
    • isInterrupted: 是一個靜態(tài)方法,它檢測當(dāng)前的線程是否被中斷。
    • 在中斷狀態(tài)被置位時調(diào)用sleep方法,它不會休眠。相反,它將清除這一狀態(tài)(!)并拋出InterruptionException。
  • 線程狀態(tài)

    getState方法。獲取線程當(dāng)前狀態(tài)。

    • New(新建)
    • Runnable(可運(yùn)行)
    • Blocked(被阻塞)
    • Waiting(等待)
    • Timed waiting (計時等待)
    • Terminated(被終止)
    image
  • 線程屬性

    • 線程優(yōu)先級

      setPriority():

      • MIN_PRIORITY (1)
      • MAX_PRIORITY (10)
      • NORM_PRIORITY (5)
    • 守護(hù)線程

      t.setDaemon(true); 必須在線程啟動之前調(diào)用

      • 當(dāng)只剩下守護(hù)線程,虛擬機(jī)退出。

        守護(hù)線程應(yīng)該永遠(yuǎn)不去訪問 固有資源,如文件、數(shù)據(jù)庫,因為他會在任何時候甚至在一個操作的中間發(fā)生中斷。

    • 未捕獲異常的處理器

      線程run 方法不能拋出任何受檢查異常, 但是, 非受檢查會導(dǎo)致線程終止。在這種情況下,線程就死亡了。

  • 同步

    • 同時被兩個線程訪問

      image
    • 鎖對象

      有兩種機(jī)制防止代碼塊受并發(fā)訪問的干擾。

      • java 語言提供一個synchronized關(guān)鍵字。

      • Java SE 5.0引入了 ReentrantLock類。

        myLock.lock(); //a ReentrantLock object
        try{
            ...
        }finally{
            myLock.unlock();//make sure the lock is unlocked even if an exception is thrown
        }
        
        //這一結(jié)構(gòu)確保任何時刻只有一個線程進(jìn)入臨界區(qū)。一旦一個線程封鎖了鎖對象,其他任何線程都無法通過lock語句。當(dāng)其他線程調(diào)用lock時,它們被阻塞,直到第一個線程釋放鎖對象。
        
        /**
         * @author chenchao
         * @Date 2018/7/4  下午2:38
         */
        public class bank {
        
            private Lock bankLock = new ReentrantLock();
        
            public void transfer(){
                bankLock.lock();
        
                try{
                    System.out.println(Thread.currentThread());
                }finally {
                    bankLock.unlock();
                }
            }
        }
        
        image
    • 條件對象

      /**
       * @author chenchao
       * @Date 2018/7/4  下午2:38
       */
      public class bank {
      
          private Lock bankLock = new ReentrantLock();
      
          //獲取條件對象
          private Condition sufficientFunds = bankLock.newCondition();
      
          public void transfer(int from, int to, int amount){
              bankLock.lock();
      
              。。。
                  
              //如果transfer發(fā)現(xiàn)余額不足,它調(diào)用await
              try {
                  while(accounts[from] < amount){
                      sufficientFunds.await(); //當(dāng)前線程被阻塞, 并放棄鎖
                      //直到另一線程調(diào)用同一條件上的signalAll方法時為止 : sufficientFunds.signalAll();
                  }
                  
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              try{
                  System.out.println(Thread.currentThread());
              }finally {
                  bankLock.unlock();
              }
          }
      }
      
      

      sufficientFunds.signalAll();這一調(diào)用重新激活因為這一條件而等待的所有線程.

      • 在while 循環(huán)中能夠確保條件滿足,執(zhí)行下面的轉(zhuǎn)賬任務(wù)。
> #### 鎖和條件的關(guān)鍵:
>
> - 鎖用來保護(hù)代碼片段,任何時刻只能由一個縣城執(zhí)行該被保護(hù)的代碼。
> - 鎖可以管理試圖進(jìn)入被保護(hù)代碼段的線程。
> - 鎖可以擁有一個或多個相關(guān)的條件對象。
> - 每個條件對象管理那些已經(jīng)進(jìn)入被保護(hù)的代碼段但還不能運(yùn)行的線程。
  • synchronized關(guān)鍵字

    從1.0版本開始,Java中每個對象都有一個內(nèi)部鎖。如果一個方法用synchronized關(guān)鍵字聲明。那么對象的鎖將保護(hù)整個方法。----------》要調(diào)用該方法,線程必須獲得內(nèi)部的對象鎖。

    • wait(): 添加一個線程到等待集中。
    • notify()/notifyAll() : 解除等待線程的阻塞狀態(tài)
    內(nèi)部鎖和條件存在一些局限性:
    • 不能中斷一個正在試圖獲得鎖的線程。

    • 試圖獲得鎖時不能設(shè)定超時。

    • 每個鎖僅有單一的條件,可能是不夠的。

##### 使用Lock和Condition對象還是同步方法?下面一些建議:

- 最好既不使用Lock/Condition也不使用synchronized關(guān)鍵字。

- 如果synchronized關(guān)鍵字適合你的程序,那么請盡量使用它,這樣可以減少編寫的代碼數(shù)量,減少出錯的幾率。

- 如果特別需要Lock/Condition結(jié)構(gòu)提供的獨(dú)有特性時,才使用Lock/Condition。
  • 同步阻塞

    每個Java對象擁有一個鎖。線程可以通過調(diào)用同步方法獲得鎖。還有另一種機(jī)制也可以獲得鎖,通過進(jìn)入一個同步阻塞。

    synchronized(obj){ //this is a syntax for a synchronized block
        ....
    }
    
    //獲得obj的鎖
    
    //有時會發(fā)現(xiàn)"特殊的"鎖
    public class Bank{
        private Object object = new Object();
        
        public void transfer(int from, int amount){
            synchronized(object){ //add ad-hoc lock
                ....
            }
        }
    }
    
  • Volatitle域

    • 多處理器的計算能夠暫時在寄存器或本地內(nèi)存緩存中保存內(nèi)存中的值。結(jié)果是,運(yùn)行在不同處理器上的線程可能同一時間在同一內(nèi)存位置取到不同的值。
    • 編譯器可以改變指令執(zhí)行的順序以使吞吐量最大化。這種順序上的變化不會改變代碼語義,但是編譯器假定內(nèi)存的值僅僅在代碼中有顯式的修改指令時才會改變。然而,內(nèi)存的值可以被另一個線程改變。

一旦一個共享變量(類的成員變量、類的靜態(tài)成員變量)被volatitle修飾之后:

1. 保證了不同線程對這個變量進(jìn)行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。

2. 禁止進(jìn)行指令重排序。

   

<font color="red">可見性只能保證每次讀取的是最新的值,但是volatitle沒辦法保證對變量的操作的原子性。</font>
  • final變量

    除非使用鎖或volatitle修飾符,否則無法從多個線程安全地讀取一個域。

    還有一種方法可以安全地訪問一個共享域,即這個域聲明為final時

  • 原子性

    可以保證即使是對個線程并發(fā)地訪問同一個實例,也會計算并返回正確的值(例:獲取自增值 )

    • Java.util.concurrent.atomic包中有很多類使用很高效的機(jī)器級指令(而不是使用鎖)來保證其他操作的原子性。

      eg:

      AtomicInteger類提供了方法incrementAndGet和decrementAndGet,它們分別以原子方式將一個整數(shù)自增或自減。

      public static AtomicLong largest = new AtomicLong();
      
      largest.updateAndGet(x -> Math(x, observed));
      //或
      largest.accumulateAndGet(obversed, Math::max);
      
- `如果有大量線程要訪問相同的原子值,性能會大幅下降,因為樂觀鎖更新需要太多次重試。`

  - java SE 8提供了`LongAdder`和`LongAccumulator`類來解決這個問題。
  - 如果認(rèn)為有大量競爭,只需使用LongAdder而不是AtomicLong.
  • 線程局部變量

    有時可能要避免共享變量,使用ThreadLocal輔助類為各個線程提供各自的實例。

    public static final ThreadLocal<SimpleDateFormat> datteFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyy-MM-dd"));
    
    //使用
    String dateStamp = dateFormat.get().format(new Date());
    
  • 線程安全的集合
    • 高效地映射、集和隊列

      • ConcurrentHashMap : 可以被多線程安全訪問的散列映射表

      • ConcurrentSkipListMap :

      • ConcurrentSkipListSet : 可以被多線程安全訪問的有序集

      • ConcurrentLinkedQueue : 可以被多線程安全訪問的無邊界非阻塞隊列

        這些集合使用復(fù)雜的算法,通過允許并發(fā)訪問數(shù)據(jù)結(jié)構(gòu)的不同部分來使競爭極小化。

  • 寫數(shù)組的拷貝

    CopyOnWriteArrayListCopyOnWriteArraySet 是線程安全的集合

    • 所有的修改線程對底層數(shù)組進(jìn)行復(fù)制。(如果在集合上進(jìn)行迭代的線程數(shù)超過修改線程數(shù),這樣的安排是很有用的。)

    • 構(gòu)建迭代器時,包含一個對當(dāng)前數(shù)組的引用。如果數(shù)據(jù)被修改,迭代器仍然引用舊數(shù)組,但是集合數(shù)組已經(jīng)被替換了。(因此,舊的迭代器擁有一致的(可能過時)的視圖, 訪問它無需任何同步開銷)。

  • 較早的線程安全

    任何集合類都可以使用同步包裝器(synchronization wrapper)變成變成安全的:

    List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());
    Map<k,v> synchHashMap = Collections.synchronizedMap(new HashMap<k, v>());
    

    最好使用java.util.concurrent包中定義的集合,不適用同步包裝器中的。

  • Callable與Future

    • Callable與Runnable類似,但是有返回值。

    • Future保存返回值的類型

      FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
                  @Override
                  public Integer call() throws Exception {
                      Thread.sleep(5000);
                      return 1;
                  }
              });
      
      Thread thread = new Thread(futureTask);
      thread.start();
      while (!futureTask.isDone()){ //判斷線程是否執(zhí)行完
          try {
              System.out.println(futureTask.get()); //直接get()會阻塞知道線程執(zhí)行完成
          } catch (InterruptedException e) {
              e.printStackTrace();
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      }
      
  • 執(zhí)行器

    • 線程池

      如果程序中創(chuàng)建了大量的生命期很短的線程,應(yīng)該使用線程池(thread pool)

      執(zhí)行器(Executor)類有許多靜態(tài)工廠方法來構(gòu)建線程池:

      image
      image
    • 預(yù)定執(zhí)行

      ScheduledExecutorService 接口具有為預(yù)定執(zhí)行 ( Scheduled Execution ) 或 重 復(fù) 執(zhí) 行 任
      務(wù)而設(shè)計的方法 。 它是一種允許使用線程池機(jī)制的 java. util . Timer 的泛化 。 Executors 類的
      newScheduledThreadPoolnewSingleThreadScheduledExecutor 方法將返回實現(xiàn)了 Scheduled
      ExecutorService 接口 的對象 。

      //預(yù)定在指定的時間之后執(zhí)行任務(wù)
      scheduledExecutorService.schedule(()-> System.out.println(Thread.currentThread().getId()), 5, TimeUnit.SECONDS);
      
      //預(yù)定在初始的延遲結(jié)束后 , 周期性地運(yùn)行給定的任務(wù) , 周期長度是 period
      scheduledExecutorService.scheduleAtFixedRate(()-> System.out.println(Thread.currentThread().getId()), 5, 2, TimeUnit.SECONDS);
      
      //預(yù)定在初始的延遲結(jié)束后周期性地運(yùn)行給定的任務(wù) , 在一次調(diào)用完成和下一次調(diào)用開始之間有長度為 delay 的延遲
      scheduledExecutorService.scheduleWithFixedDelay(()-> System.out.println(Thread.currentThread().getId()), 5, 2, TimeUnit.SECONDS);
      
  • 控制任務(wù)組

    // invokeAny 方法提交所有對象到一個 Callable 對象的集合中, 并返回某個已經(jīng)完成了的任務(wù)的結(jié)果
    List<Callable<T>> tasks = ...;
    List<Future<T>> results = executor.invokeAll(tasks);
    for(Future<T> result : results)
        processFurther(result.get());
    //這個方法的缺點(diǎn)是如果第一個任務(wù)恰巧花去了很多時間,則可能不得不進(jìn)行等待。
    

    將結(jié)果按可獲得的順序保存起來更有實際意義 。 可以用 ExecutorCompletionService來進(jìn)行排列 。

    ExecutorCompletionService<T> service = new ExecutorCompletionService<>(executor);
    for(Callable<T> task : tasks)
        service.submit(task);
    for(int i = 0; i < tasks.size(); i++){
        processFurther(service.take().get());
    }
    
  • Fork-Join框架

    在后臺,fork-join框架使用了一種有效的只能方法來平衡可用線程的工作負(fù)載,這種方法稱為工作密取(work stealing)。每個工作線程都有一個雙端隊列(deque)來完成工作。一個工作線程將子任務(wù)壓入其雙端隊列的對頭。(只有一個線程可以訪問隊頭,所以不需要加鎖)一個工作線程空閑時,它會從另一個雙端隊列的隊尾“密取”一個任務(wù)。由于大的子任務(wù)都在隊尾,這種密取很少出現(xiàn)。

    /**
     * @author chenchao
     * @Date 2018/8/2  下午2:33
     */
    public class ForkJoinTest {
    
      public static void main(String[] args) {
        final int SIZE = 1000000;
        double[] numbers = new double[SIZE];
        for (int i = 0; i < SIZE; i++) {
          numbers[i] = Math.random();
        }
    
        Counter counter = new Counter(numbers, 0, numbers.length, x -> x>0.5);
    
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        forkJoinPool.invoke(counter);
        System.out.println(counter.join());
      }
    }
    class Counter extends RecursiveTask<Integer>{
    
      public static final int THRESHOLD = 1000;
      private double[] values;
      private int from;
      private int to;
      private DoublePredicate filter;
    
      public Counter(double[] values, int from, int to, DoublePredicate filter){
        this.values = values;
        this.from = from;
        this.to = to;
        this.filter = filter;
      }
    
      @Override
      protected Integer compute() {
    
        if(to - from < THRESHOLD){
          int count = 0;
          for (int i = from; i < to; i++) {
            if(filter.test(values[i])){
              count++;
            }
          }
          return count;
        }else {
          int mid = (from + to)/2;
          Counter first = new Counter(values, from, mid, filter);
          Counter second = new Counter(values, mid, to, filter);
          invokeAll(first, second);
          return first.join() + second.join();
        }
      }
    }
    
  • 可完成Future

    Java SE8 的CompletableFuture

    eg: 從一個頁面抽取所有鏈接來建立一個爬蟲。

    CompletableFuture<String> contents = readPage(url);
    CompletableFuture<List<URL>> links = contents.thenApply(Parser::getLinks);
    
    //thenApply不會阻塞,返回另一個future,第一個future完成時其結(jié)果會提供給getLonks方法,這個方法的返回值就是希望的最終結(jié)果。
    

    <font color="red">利用可完成future,可以指定你希望做什么,以及希望以什么順序執(zhí)行這些工作</font>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,652評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評論 19 139
  • 文/郁嵐 1.對不起,我又想你了 你有沒有曾經(jīng)深愛過那么一個人,他是刻進(jìn)生命里的疼,不時一...
    郁嵐閱讀 708評論 2 0
  • 正開指的產(chǎn)婦,跳下窗臺那刻,也許真的只有疼痛的絕望。 沒有順產(chǎn)生過小孩,沒有經(jīng)歷過厭世的悲涼,我想男人也許沒有足夠...
    亦塵溪閱讀 560評論 0 1
  • 江湖說:突如其來的脾氣,是因為積攢了太多的怨氣! 文/螢火妖圖/來自網(wǎng)絡(luò) 無論是陽光正好還是陰雨朦朧的日子我喜歡這...
    螢火妖閱讀 844評論 0 2

友情鏈接更多精彩內(nèi)容