JAVA 基礎問題

記錄JAVA一些容易疏忽基礎問題

 

基礎

Q 基本變量聲明注意html

eg1:  float f=3.4,錯誤。雙精度轉單精度,下轉型,需強轉。float f =(float)3.4; 或者寫成float f =3.4F;java

eg2:   short s1 = 1; s1 = s1 + 1。錯誤,理由同上。short s1 = 1; s1 += 1。正確,由於s1+= 1;redis

至關於s1 = (short)(s1 + 1),其中有隱含的強制類型轉換。算法

eg3:     long w=12345678901。錯誤。超出了int類型的取值範圍。正確: long w=12345678901L spring

Q 若是int x=20, y=5,則語句System.out.println(x+y +""+(x+y)+y);  的輸出結果是sql

https://www.nowcoder.com/profile/1073556/myFollowings/detail/6908631數據庫

答案:25255數組

Q 若是int x=20,則語句System.out.println(x++ + ++x +x);  的輸出結果是緩存

答案:20+22+22=64,前取值在運算/先運算在取值安全

Q char能夠保存中文?

答案:佔2字節,只能保存一箇中文,char c = '我'; 注意不能用""

Q.如下代碼的輸出結果是?

public class Test{
static{
   int x=5;
}
static int x,y;
public static void main(String args[]){
   x--;
   myMethod( );
   System.out.println(x+y+ ++x);
}
public static void myMethod( ){
  y=x++ + ++x;
 }
}

答案:3 ,此題坑是靜態代碼塊中是局部變量x,不是靜態變量x 

Q.下面這三條語句

1

2

3

System.out.println(「is 」+ 100 + 5);

System.out.println(100 + 5 +「 is」);

System.out.println(「is 」+ (100 + 5));

的輸出結果分別是? is 1005, 105 is, is 105

Q 輸出結果

public class Inc { 
    public static void main(String[] args) { 
       Inc inc = new Inc(); 
       int i = 0; 
       inc.fermin(i); 
       i= i ++; 
       System.out.println(i);
    
    } 
    void fermin(int i){ 
       i++; 
    } 
}

 答案:0,此題考值傳遞,坑  i= i ++;  先取值在運算

Q  如下代碼輸出的是

public class SendValue{
    public String str="6";
    public static void main(String[] args) {
        SendValue sv=new SendValue();
        sv.change(sv.str);
        System.out.println(sv.str);
    }
    public void change(String str) {
        str="10";
    }
}

 答案:"6" 值傳遞

Q 輸出結果

public class StringDemo{
  private static final String MESSAGE="taobao";
  public static void main(String [] args) {
    String a ="tao"+"bao";
    String b="tao";
    String c="bao";
    System.out.println(a==MESSAGE);
    System.out.println((b+c)==MESSAGE);
  }
}

答案:true false; 編譯時"tao"+"bao"將直接變成"taobao",b+c則不會優化,由於不知道在以前的步驟中bc會不會發生改變,而針對b+c則是用語法糖,新建一個StringBuilder來處理

Q.hashcode問題

兩個對象相等equals(),必須有相同的hashcode 值,反之不必定。同一對象的 hashcode 值不變

兩個不相等!equals()的對象可能會有相同的 hashcode 值,這就是爲何在 hashmap 中會有衝突。

因此其get方法是先判斷hashcode相同,若相同還要判斷equals。

Q 取模Hash分片,一致性Hash分片

這兩種算法都是經過數據的hash來給數據分片,從而達到分治、分段、分庫分表的效果。

可是一致性Hash算法,後期的擴容成本和數據遷移成本要更好。其擴容後只須要遷移少許的數據。

而取模算法擴容後,則會影響到全部的數據分片,須要大量的數據遷移,才能最終完善。

經過取模法 實現最簡單的分表

index = hash(sharding字段) % 分表數量 ;

select xx from 'busy_'+index where sharding字段 = xxx;

Q.哪些是不合法的定義?

public class Demo{
  float func0()
  {
    byte i=1;
    return i;
  }
  float func1()
  {
    int i=1;
    return;
  }
  float func2()
  {
    short i=2;
    return i;
  }
  float func3()
  {
    long i=3;
    return i;
  }
  float func4()
  {
    double i=4;
    return i;
  }
}

答案:func一、func4,重點是類型轉換圖。哪些能夠自動轉換,哪些須要強制轉換

Q 位運算更高效

<<  n 乘2的N次方    >> n  除2的N次方

Q JAVA引用類型

依次減弱 

Q final關鍵字

其能夠做用在類、方法、成員變量之上。

final成員變量是不可變的:基本變量不可變,引用變量其引用不可變,可是引用的對象內部狀態是不受限制的。

final變量不可變因此其是絕對的線程安全的,也是能夠保證安全發佈的。

final變量必須保證最晚在構造函數中賦值/初始化,不要忘記無參構造函數也要初始化final變量

public  class SimLockInfo{

    private final Long sn;
    private final AtomicInteger retry;

    public SimLockInfo() {
        //初始化final變量
        retry = null;
        sn = null;
    }

    public SimLockInfo(Integer dhhm, Long sn, String id, Boolean get, Long begintime) {
        this.sn = sn;
        this.retry=new AtomicInteger(0);
    }
}

 

 

String

Q String類爲何是final類。 

0.類安全:不會被繼承修改,沒有子類能夠替代

1.線程安全 :不可變對象

2.支持字符串常量池數據共享,節省資源,提升效率(由於若是已經存在這個常量便不會再建立,直接拿來用)

由於是不可變對象,因此hashcode不會改變,String對象緩存了本身的hash值。這樣其做爲Map的Key時會有更高的效率。

Q String怎麼實現不可變?

https://blog.csdn.net/u010648555/article/details/89819686

字符串是常量,它們的值在建立以後不能更改。能夠看到字符串的更改相關方法是返回新的字符串實例,而不是更改當前字符串的value[], String是不可變的關鍵都在底層的實現,而不是隻是一個final。

//部分源碼
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];
    
    //拼接
	public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        // 從新建立一個新的字符串
        return new String(buf, true);
	}
}

Q String不可變測試。

@Test
    public void str(){
        getstr("s",1);
    }

    private void getstr(String s, int i){
        if (i>2)
            return ;

        System.out.println(i+"層遞歸進入前:"+s);
        getstr(s+"s",i+1);   //s不可變,+拼接返回new String。下層引用的是new String
        System.out.println("遞歸返回到"+i+"層:"+s);
    }

---輸出結果
1層遞歸進入前:s
2層遞歸進入前:ss
遞歸返回到2層:ss
遞歸返回到1層:s

 

Q String常量池的意義。

減小字符串的建立減小內存開銷。假如沒有字符串常量池,下面這個方法每次建立方法棧幀都要實例化兩個字符串,可是經過常量池就能夠避免這種問題。

public void getName(){
   String s="123";
   String name=getInfo("NAME",s)
}

 

 

數據類型

Q 判斷integer

int h=9;
double j=9.0;
Integer a=new Integer(9);
Integer b=new Integer(9);
Integer c = 9;
Integer d = 9;
Double i= 9.0;
Integer e =Integer.valueOf(9);
Integer k =Integer.valueOf("9"); //類型轉換 用包裝類完成
int l=Integer.valueOf(9).intValue();

Integer f = 128;
Integer g = 128;

答案:

h==j;//true 
a==h;//true 自動拆箱
a==b;//false
a==c;//false
c==d;//true
c==e;//true
f==g;//false
i.equals(h)//false
i.equals(j)//true

一、基本型和基本型封裝型進行「==」運算符的比較,基本型封裝型將會自動拆箱變爲基本型後再進行比較,所以Integer(0)會自動拆箱爲int類型再進行比較,顯然返回true;

         int a = 220;

         Integer b = 220;

        System.out.println(a==b);//true
二、兩個Integer類型進行「==」比較, 若是其值在-128至127  ,那麼返回true,不然返回false, 這跟Integer.valueOf()的緩衝對象有關,這裏不進行贅述。

        Integer c=3;
        Integer h=3;
        Integer e=321;
        Integer f=321;
        System.out.println(c==h);//true
        System.out.println(e==f);//false
三、兩個基本型的封裝型進行equals()比較,首先equals()會比較類型,若是類型相同,則繼續比較值,若是值也相同,返回true。

        Integer a=1;
        Integer b=2;
        Integer c=3;
        System.out.println(c.equals(a+b));//true
四、基本型封裝類型調用equals(),可是參數是基本類型,這時候,先會進行自動裝箱,基本型轉換爲其封裝類型,再進行3中的比較。  

        int i=1;
        int j = 2;
        Integer c=3;
        System.out.println(c.equals(i+j));//true

 

Q 設有下面兩個賦值語句:a,b類型?

a = Integer.parseInt("1024");

b = Integer.valueOf("1024").intValue();

答案:a和b都是整數類型變量而且它們的值相等。

Q  有以下4條語句

Integer i01 = 59;
int i02 = 59;
Integer i03 =Integer.valueOf(59);
Integer i04 = new Integer(59);
System.out.println(i01 == i02); //true  Integer 會自動拆箱成 int ,而後進行值的比較
System.out.println(i01 == i03); //true  Integer內部緩存
System.out.println(i03 == i04); //false
System.out.println(i02 == i04); //true  Integer 會自動拆箱成 int ,而後進行值的比較

Q 敏感的小數不要使用double和float

這兩種類型容易引起精度丟失,或出現比較偏差,例如:1.0-0.9=0.09999999....

方案一:使用BigDecimal類型作小數計算。必須由String轉化

    若BigDecimal與Double等其餘類型比較,請將Double轉化爲BigDecimal,而後用BigDecimal.compareTo作比較。

    可是以上方案還有可能出現0.1與0.10比較有偏差的狀況,或是Double的0.1轉BigDecimal後出現0.100001的狀況,此時能夠用減法計算,若差值小於一個閾值時認定爲相等。

推薦先將double轉String在轉BigDecimal

https://blog.csdn.net/weixin_37864013/article/details/78232046

============類型轉換================

 System.out.println(new BigDecimal(1.22));
//1.2199999999999999733546474089962430298328399658203125

System.out.println(new BigDecimal(String.valueOf(1.22)));
//1.22

============計算2/3=================

float f1= 2/3;
System.out.println(f1);
//0.0 計算錯誤

float f= (float) (new Double(2)/3);
System.out.println(f);
//0.6666667  

BigDecimal s = new BigDecimal("2");
System.out.println(s.divide(new BigDecimal("3"), 5 ,BigDecimal.ROUND_HALF_UP));
//0.66667 注意除不盡時必定要設置divide方法的保留位數和四捨五入策略,不然拋出異常

方案二:全部金額已分爲單位,用long記錄。

如果在遇到除不盡問題,則最後作補償處理。

 以上方案一樣適用於數據庫。

BigDecimal

https://blog.csdn.net/weixin_37864013/article/details/78232046

https://blog.csdn.net/u011781521/article/details/80052195

http://www.javashuo.com/article/p-wjjqzjns-mv.html

   (1)商業計算使用BigDecimal。

   (2)儘可能使用參數類型爲String的構造函數。

  (3)  BigInteger與BigDecimal都是不可變的(immutable),在進行每一步運算時,都會產生一個新的對象,因此在作加減乘除運算時千萬要保存操做後的值。

  (4)全部加減乘除運算,都不是修改當前 BigDecimal對象 而是將結果做爲新對象返回,與(3)相關

BigDecimal a= new BigDecimal("1.22");
a.divide(new BigDecimal(2));
System.out.println(a);
//1.22

a=a.divide(new BigDecimal(2));
System.out.println(a);
//0.61

經常使用API

add(BigDecimal)        BigDecimal對象中的值相加,而後返回這個對象。 
subtract(BigDecimal) BigDecimal對象中的值相減,而後返回這個對象。 
multiply(BigDecimal)  BigDecimal對象中的值相乘,而後返回這個對象。 
divide(BigDecimal)     BigDecimal對象中的值相除,而後返回這個對象。 
toString()                將BigDecimal對象的數值轉換成字符串。 
doubleValue()          將BigDecimal對象中的值以雙精度數返回。 
floatValue()             將BigDecimal對象中的值以單精度數返回。 
longValue()             將BigDecimal對象中的值以長整數返回。 
intValue()               將BigDecimal對象中的值以整數返回。

類初始化

Q.如下代碼的錯誤在哪裏?

public class Test {

public static int i=1;

static {
        i=2;
        j=2;
        System.out.println(i);
        System.out.println(j);
}

public static int j=1;

}

答案: 只有System.out.println(j)會編譯失敗。  static代碼在類加載的過程當中執行, 且類加載是按照書寫順序加載代碼。

"j" 在靜態代碼快中,只能賦值不能調用。

Q.如下代碼的輸出結果是?

https://www.nowcoder.com/test/question/done?tid=20172030&qid=14700#summary

public class B
{
    public static B t1 = new B();
    public static B t2 = new B();
    {
        System.out.println("構造塊");
    }
    static
    {
        System.out.println("靜態塊");
    }
    public static void main(String[] args)
    {
        B t = new B();
    }
}

答案:構造塊 構造塊 靜態塊 構造塊

靜態塊只會在首次加載類時執行,在類第一次加載時已經肯定首次,即便未執行完也是首次加載。

Q.運行下面代碼,輸出的結果是

class A {
 
public A() {System.out.println("class A");}
 
{ System.out.println("I'm A class"); }
  
static { System.out.println("class A static"); }
  
}
 
public class B extends A { 

public B() { System.out.println("class B"); }
 
static { System.out.println("class B static"); } 
 
{ System.out.println("I'm B class"); }
 
public static void main(String[] args) { 
   new B();
}  
}

答案:只要說明父子類初始化順序,父靜-子靜-父普-子普

父靜態成員初始化 -- 父靜態代碼塊 -- 子靜態成員初始化 -- 子靜態代碼塊--父成員變量初始化 -- 父代碼塊 --父構造器-- 子成員初始化 -- 子代碼塊--子構造器

class A static        
class B static           
I'm A class             
class A               
I'm B class 
class B

Q.運行下面代碼,輸出的結果是

public class Cshsx {

	private int i = 1;
	public static int s = 2;

	static {
		System.out.println("靜態代碼塊");
		System.out.println(s);
	}

	{
		System.out.println("代碼塊");
		System.out.println(i);
		System.out.println(s);
	}

	public static void main(String[] args) {
		new Cshsx();
	}
}

答案:主要是說明成員變量的初始化和賦值,在相應的代碼塊以前執行

靜態代碼塊
2
代碼塊
1
2

Q.運行下面代碼,輸出的結果是

class X{
    Y y=new Y();
    public X(){
        System.out.print("X");
    }
}
class Y{
    public Y(){
        System.out.print("Y");
    }
}
public class Z extends X{
    Y y=new Y();
    public Z(){
        System.out.print("Z");
    }
    public static void main(String[] args) {
        new Z();
    }
}

答案:YXYZ

 

繼承

Q.如下代碼執行的結果顯示是?

https://www.nowcoder.com/test/question/done?tid=20172030&qid=112823#summary

public class Demo { 
    class Super{  
         int flag=1;
         Super(){
             test();
         }  
         void test(){
            System.out.println("Super.test() flag="+flag);
         }
    } 
    class Sub extends Super{
        Sub(int i){ 
            flag=i;
            System.out.println("Sub.Sub()flag="+flag);
        }  
        void test(){
            System.out.println("Sub.test()flag="+flag);
        }
    } 
    public static void main(String[] args) {  
           new Demo().new Sub(5);
    }
}

答案:

Sub.test() flag=1

Sub.Sub() flag=5

Q.如下代碼執行的結果顯示是?

https://www.nowcoder.com/profile/1073556/myFollowings/detail/6618019

public class Main
{
    private String baseName = "base";
    public Main()
    {
        callName();
    }

    public void callName()
    {
        System.out.println(baseName);
    }

    static class Sub extends Main
    {
        private String baseName = "sub";
        public void callName()
        {
            System.out.println (baseName) ;
        }
    }
    public static void main(String[] args)
    {
        Main b = new Sub();
    }
}

答案:null,子類構造器執行super(),因爲子類中存在變量baseName和方法callName,因爲動態綁定和覆蓋,super()調用的是子類方法,子類方法調用子類變量。可是此時子類非靜態成員變量 baseName只完成初始化null,尚未賦值"sub"。

繼承中:只有子類可見的同名方法會被子類覆蓋,可是父類方法仍是存在的,能夠在子類中經過super調用。

因此 全部的 變量+方法 都是父子相互獨立共存。靜態內容根據當前對象的父/子引用變量類型,調用對應類的靜態內容。

非靜態內容動態綁定。

Q.判斷對錯。在java的多態調用中,new的是哪個類就是調用的哪一個類的方法。

答案:錯,動態綁定/靜態綁定

final,static,private,構造器  是靜態綁定方法不能覆蓋

子類可見的非靜態方法能夠覆蓋(三同一大一小原則),運用的是動態單分配,是根據new的類型肯定對象,從而肯定調用的方法;

靜態方法同名獨立共存不覆蓋,運用的是靜態多分派,即根據靜態類型肯定對象(即引用變量類型,決定調用哪一個類的靜態方法),所以不是根據new的類型肯定調用的方法

Q.如下代碼執行的結果是多少

https://www.nowcoder.com/profile/1073556/myFollowings/detail/5986970

public class Demo {
public static void main(String[] args) {
    Collection<?>[] collections = {new HashSet<String>(), new ArrayList<String>(), new HashMap<String, String>().values()};
    Super subToSuper = new Sub();
    for(Collection<?> collection: collections) {
        System.out.println(subToSuper.getType(collection));
    }
}
abstract static class Super {
    public static String getType(Collection<?> collection) {
        return 「Super:collection」;
    }
    public static String getType(List<?> list) {
        return 「Super:list」;
    }
    public String getType(ArrayList<?> list) {
        return 「Super:arrayList」;
    }
    public static String getType(Set<?> set) {
        return 「Super:set」;
    }
    public String getType(HashSet<?> set) {
        return 「Super:hashSet」;
    }
}
static class Sub extends Super {
    public static String getType(Collection<?> collection) {
            return "Sub"; }
}
}

答案:

Super:collection
Super:collection
Super:collection

考察點1:重載靜態多分派——根據傳入重載方法的參數類型,選擇更加合適的一個重載方法

考察點2:static方法不能被子類覆寫,在子類中定義了和父類徹底相同的static方法,則父類的static方法被隱藏,Son.staticmethod()或new Son().staticmethod()都是調用的子類的static方法,若是是Father.staticmethod()或者Father f = new Son(); f.staticmethod()調用的都是父類的static方法。

考察點3:此題若是都不是static方法,則最終的結果是A. 調用子類的getType,輸出collection

Q.對文件名爲Test.java的java代碼描述正確的是()

https://www.nowcoder.com/test/question/done?tid=20172030&qid=25612#summary

class Person {
    String name = "No name";
    public Person(String nm) {
        name = nm;
    }
}
class Employee extends Person {
    String empID = "0000";
    public Employee(String id) {
        empID = id;
    }
}
public class Test {
    public static void main(String args[]) {
        Employee e = new Employee("123");
        System.out.println(e.empID);
    }
}

答案:編譯報錯,由於子類構造器中隱式調用super(),可是父類沒有無參構造器

子類構造器中會隱式/顯示調用父類構造器,但其初始化執行在子類成員變量初始化以前。父類構造器的初始化執行順序,請看父子類初始化順序

Q.如下代碼執行的結果是?

public class Father { 
protected void doSomething() { 
System.out.println ("Father doSomething " ); 
this.doSomething(); 
}

public static void main(String[] args) { 
Father father= new Son(); 
father.doSomething() ; 
}

class Son extends Father { 
@Override
public void doSomething() { 
System.out.println ("Son ’ s doSomething " ); 
super.doSomething(); 
}
}

答案:如下代碼會出現棧溢出 StackOverflow Error ,由於doSomething方法會無線循環。

 

Try catch finally

Q 在try的括號裏面有return一個值,那在哪裏執行finally裏的代碼?

答案:return前執行

Q下面代碼運行結果是()

public class Test{ 
    public int add(int a,int b){   
         try {
             return a+b;      
         } 
        catch (Exception e) {  
            System.out.println("catch語句塊");
         }
         finally{ 
             System.out.println("finally語句塊");
         }
         return 0;
    } 
     public static void main(String argv[]){ 
         Test test =new Test(); 
         System.out.println("和是:"+test.add(9, 34)); 
     }
}

答案:

finally語句塊
和是:43

Q 最終輸出?

public class Main {
    public static int add(){
        try {
            return 5;
        }
        catch (Exception e) {
        }
        finally{
            return 10;
        }
    }

    public static void main(String[] args) {
        System.out.println(add());
    }
}

答案:10

Q 最終輸出?

public class Main {
    public static int add(){
        int a=1;
        try {
            return a;
        }
        catch (Exception e) {
        }
        finally{
           a=10;
        }
        return 0;
    }

    public static void main(String[] args) {
        System.out.println(add());
    }
}

答案:1,finally改變不了return過的基本變量

Q 最終輸出?

public class Main {
    public static String add(){
        String a="123";
        try {
            return a;
        }
        catch (Exception e) {
        }
        finally{
           a="456";
        }
        return "";
    }

    public static void main(String[] args) {
        System.out.println(add());
    }
}

答案:123 ,finally改變不了return過的引用變量

異常

Q.RuntimeException和Exception的區別

非RuntimeException即便throws上拋,最終也必需被catch處理。

RuntimeException容許不catch捕捉,方法也不用throws,其會致使代碼運行中斷。通常程序裏自定義異常繼承RuntimeException表示代碼級別錯誤。RuntimeException通常會表明開發人員觸發的錯誤。

public class BaseException extends RuntimeException {
    private int status = 200;

    public BaseException(String message,int status) {
        super(message); //繼承父類String message;
        this.status = status;
    }

    getter()+setter();
}

控制檯異常打印順序

CauseBy倒序打印,最下邊的CauseBy是最早觸發E被打印出來的。每一個CauseBy中的異常方法是順序打印,先看第一個方法

 

容器(集合)

Q JAVA提供的集合都有哪些,特色是什麼,使用場景?

https://blog.csdn.net/carson_ho/article/details/85043628

https://blog.csdn.net/sdgihshdv/article/details/72566485

Q Collections工具類經常使用API

排序
static void sort(List<T> list, Comparator<? super T> c);
static T max(Collection<? extends T> coll, Comparator<? super T> comp); 
static T min(Collection<? extends T> coll, Comparator<? super T> comp); 

查找
static <T> int	binarySearch(List<? extends T> list, T key, Comparator<? super T> c);

複製、替換
static <T> void	copy(List<? super T> dest, List<? extends T> src);
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)

雜項(無交集?、元素出現次數、List元素填充、List子串位置、翻轉List、亂序List)
static boolean	disjoint(Collection<?> c1, Collection<?> c2);  
static <T> void	fill(List<? super T> list, T obj);   
static int	frequency(Collection<?> c, Object o);    
static int	indexOfSubList(List<?> source, List<?> target);    
static int	lastIndexOfSubList(List<?> source, List<?> target);
static void	reverse(List<?> list);   
static void	shuffle(List<?> list);   

同步包裝(包含全部類型,只以Collection爲例子)
static <T> Collection<T>	synchronizedCollection(Collection<T> c);

檢查(包含全部類型,只以List爲例子)
static <E> List<E> checkedList(List<E> list, Class<E> type);

不可變包裝(包含全部類型,只以Collection爲例子)
static <T> Collection<T>	unmodifiableCollection(Collection<? extends T> c);

 

Q 若已知要產生一個要保存大量Object的容器,怎樣提升代碼執行效率?

答案:初始化容器時,指定初始容量,避免反覆擴容。

全部以數組爲基礎的數據結構都涉及到擴容問題。Arrays.copyOf()

hashMap結構因爲是數組+鏈表+紅黑樹,初始設置爲2的N次冪。例如:2000個元素,new hashMap(128)

Q HashMap負載因子

負載因子LoadFactor:決定填充比例,默認0.75。Threshold(閾值)=LoadFactor*Capacity(map的容量) ,Threshold爲Map的真實容量。

LoadFactor越大利用率越高,越小分佈越均勻查找速度越快。

Q List中 subList 只是父List的視圖,並非拷貝,修改父或子的內容,至關於所有修改。

   且subList 是一種內部類List。能夠用List接口的引用變量。

 

Q 在 subList 場景中,高度注意對父集合元素個數的修改,會致使子集合的遍歷、增長、
刪除均會產生 ConcurrentModificationException 異常。

Q Arrays.asList()把數組轉換成集合,有什麼問題?

1 他返回的Arrays中的內部類其實現List接口,能夠用List接口的引用變量

2 沒有實現List接口的修改方法, 使用add/remove/clear 方法會拋出 UnsupportedOperationException 異常。

  若想轉化成ArrayList,能夠new ArrayList(Arrays.asList());

3 其只是原數組的視圖,不是拷貝,修改原數組,其值也改變。

  str[0] = "gujin"; 那麼 list.get(0)也會隨之修改。

Q.List 轉數組推薦不要直接用 toArray 無參方法?

   由於其只能返回Object[]類,對泛型不友好

//推薦操做,保證數組類型
String[] array = new String[list.size()]; 
array = list.toArray(array); 
//array的大小等於list時效率最高,小於則array全部爲NULL,大於效率慢

Q List迭代中修改集合注意?

不要在 for/foreach 循環裏進行元素的 remove/add 操做,可能會產生 ConcurrentModificationException 異常,或是難以預測的錯誤結果。推薦使用 Iterator/ListIterator 接口中的remove/add。注意:Iterator只能next+remove。

在Iterator遍歷的過程當中,必定要使用Iterator/ListIterator接口中的修改方法。集合自身的修改方法在Iterator外部使用。

Q 容器遍歷 fail-fast 和 fail-safe

fail-fast:快速失敗。當前線程記錄遍歷開始的容器修改次數modCount,遍歷過程當中檢查有無變化。有變化說明容器被修改(多是當前線程也多是其餘線程修改),拋出ConcurrentModificationException。

fail-safe:至關於先拷貝容器快照,遍歷這個快照而再也不關心容器自己的變化。缺點是可能出現弱一致性問題。JAVA併發包中的全部容器均採用此機制。

Q CopyOnWriteArrayList 

適合讀多寫不多的狀況,寫操做時加鎖順序執行,複製集合,並在複製集合上添加/刪除操做,而後用複製集合替換當前集合的引用。

COW注意:1.設置合理的初始容量;2.修改效率很低,最好能批量添加/刪除(addAll或removeAll),能夠先寫到ArrayList中。

Q List 集合高效遍歷?

if (list instanceof RandomAccess) {
    //使用傳統的for循環遍歷。
} else {
    //使用Iterator或者foreach。
}

Q.HashMap,自定義類型對象做爲KEY,注意點?

答案:自定義類型,最好重寫hashcode()和equels()。Map爲提升效率會用hashcode()判斷KEY是否相等。

若是不重寫,默認使用Object類中hashcode()。判斷的是KEY內存地址。HashMap容許KEY=NULL。

Q Map 集合高效遍歷?

使用 map.entrySet +Iterator遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。JDK8可使用 Map.foreach 方法。

values()返回的是 Collection<V> 值集合,是一個 List 集合對象;keySet()返回的是 K 值集合,是一個 Set 集合對象;entrySet()返回的是 K-V 值組合集合。

Q Map中的視圖keySet, values, entrySet

Map中保存着 keySet, values, entrySet三個視圖。這些返回的視圖是支持清除操做(刪除元素後,MAP也隨之改變),也能夠修改元素的狀態, 可是增長元素會拋出異常。由於AbstractCollection 沒有實現 add 操做 可是實現了 remove 、clear 等相關操做

Q Hashmap 對比 TreeMap

綜合推薦 HashMap,其結合了紅黑樹的優勢,且修改效率相對高一些

1.HashMap 使用 hashCode+equals 實現去重的。而 TreeMap 依靠 Comparable+Comparator 來實現 Key 的去重。

2.HashMap插入和刪除效率高於TreeMap。由於TreeMap調整紅黑樹的成本更高。

3.TreeMap的查找效率高於HashMap。由於TreeMap是純紅黑樹類型。HashMap是哈希表+紅黑樹

Q List 集合快速去重複?

//藉助Set特性和hash表, 爲List去重
hashSet.addAll(arrayList); 
newList.addAll(hashSet);

Q 集合交併差統計等高效操做思路與數據結構?(當內容數量很少時,也能夠考慮暴力處理)

本人原文內容比較多,因此單開一頁:

https://blog.csdn.net/shanchahua123456/article/details/86518172

https://blog.csdn.net/has330338724/article/details/50532901

Q Top K 問題?

其本質上是排序問題,經常使用思路:快速排序(求小 左遞歸,求大 右遞歸)、堆排序(適合大數據量) .。

常規排序缺點:僅僅爲找出前幾個元素,而將全部元素排序,浪費時間和資源

堆排序雖然自己速度沒有快排和歸併這種分治排序快,可是當統計外部大數據量時,求TOP K只須要創建容量爲K的堆,很是節省內存開銷。

JAVA中PriorityQueue 優先級隊列是小根堆結構,可是能夠經過comparator自定義大小,轉換成大根堆模式。

PriorityQueue(int initialCapacity,Comparator<? super E> comparator)

Q 集合第三方JAR推薦?

google.Guava , Apache Commons Collections 包含不少高效的集合操做API和新類型的數據結構

推薦Guava,經測試平均執行效率高於JDKAPI(Lists,Sets,Maps,Collections2等工具類中提供了包括集合交併差過濾等多種高效操做)。並且除了集合,還有不少高效簡單的工具類,涉及String,Object,I/O等等

http://www.javashuo.com/article/p-rhviocqv-nn.html

Q 實現LRU緩存

 Map<Integer, String> map = new LinkedHashMap<Integer, String>(){

            private static final long    serialVersionUID    = 1L;
            private int maxSize = 10;

            @Override
            protected boolean removeEldestEntry(java.util.Map.Entry<Integer, String> pEldest) {
                return size() > maxSize;
            }

        };

泛型

Q 集合泛型注意事項

1 List<T>泛型容器變量在賦值無泛型容器變量時,不檢查泛型約束,可是在使用時會編譯失敗。

  List<T> 在引用變量肯定T類型後,就不能接收不一樣List<E>的對象引用。

List a1=new ArrayList();  //無泛型
a1.add("123");
a1.add(1);

List<Integer> a2=a1;  //此處正常編譯
Integer i=a2.get(0);  //編譯出錯

  假如T是E的父類,可是List<T> 和List<E> 沒有父子關係。引用變量不能相互使用。

2 List<?>引用 :能夠接受任意類型集合引用,可是其不能執行add元素操做。能夠remove元素。

3 List<? extends T>引用 :只接受子類,上界爲T(包含T)。不一樣於1,其引用賦值時會檢查泛型約束。除了add(null)外沒法執行add操做。get操做返回T類型對象。適合讀

4 List<? super T>引用 :只接受父類,下界爲T(包含T)。其引用賦值時會檢查泛型約束。只能add添加T類或其子類。get操做返回Object類型對象。適合寫

下面兩段代碼,能夠看出

無泛型集合轉換成有泛型集合,只要類型匹配就能正確的進行類型轉換

有泛型集合轉換另外一種泛型集合,即便類型匹配,IDE也會檢查出錯誤。

無泛型引用和任意泛型引用能夠相互賦值(運行時自動轉換類型,轉換不匹配拋異常),有泛型引用只能與同泛型引用賦值。

@Test
    public void testFx(){
      List<SimCard> simCards=getsimlist();
       //能夠正確執行
        for (SimCard s:simCards){
            System.out.println(s.getDhhm());
        }
    }

   //返回無泛型List 
    private List getsimlist(){
        ArrayList<Object> simCards = new ArrayList(3);
        simCards.add(new SimCard("1","1","1","1"));
        simCards.add(new SimCard("2","1","1","1"));
        return  simCards;
    }
@Test
    public void testFx(){
      //IDE提示錯誤
      List<SimCard> simCards=getsimlist(); 
      
       for (SimCard s:simCards){
            System.out.println(s.getDhhm());
        }
    }

   //返回有泛型List 
    private List<Object>  getsimlist(){
        ArrayList<Object> simCards = new ArrayList(3);
        simCards.add(new SimCard("1","1","1","1"));
        simCards.add(new SimCard("2","1","1","1"));
        return  simCards;
    }

 

Q 泛型方法

泛型方法經過返回值引用,參數類型自動推斷泛型,不須要調用時指定<T,E>。

使用時注意類型檢測。

--調用
Pojo pojo =getObject("1");


--<T,E>自動推斷<Pojo,String>
private <T,E> T getObject(E name){
       return (T) redisTemplate.opsForValue().get(name);
}

Q 泛型測試

class TestType<T> {
    private T arg;

    public T getArg() {
        return arg;
    }

    public void setArg(T arg) {
        this.arg = arg;
    }

    public  <T> T getAtgT(T name){
        System.out.println(name);
        return name;
    }

    public static void main(String[] args) {
        TestType<Integer> integerTestType = new TestType<>();
        integerTestType.setArg(1);
        System.out.println(integerTestType.getArg());
        String s= integerTestType.getAtgT("123");
        Double d=integerTestType.getAtgT(new Double(1));

    }
}

當integerTestType實例化後,其類泛型的屬性和方法就已經綁定好。可是泛型方法getAtgT()仍然是獨立的類型T。

泛型方法getAtgT()能夠根據每次的參數類型,獨立推斷本身的泛型

 

事務

Q.spring的PROPAGATION_REQUIRES_NEW事務,下面哪些說法是正確的?

答案:內部事務回滾了,外部事務仍然能夠提交

PROPAGATION_REQUIRES_NEW 啓動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被徹底 commited 或 rolled back 而不依賴於外部事務, 它擁有本身的隔離範圍, 本身的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行

Q.spring的事務註解在加synchronized鎖時有沒有可能出現問題

答案:要保證synchronized包裹事務方法,先進入同步鎖再啓動事務就不會有問題。不然由於數據庫隔離性的特色引發問題。下面是有問題的代碼:

@Transactional
public synchronized void update() throws Exception {
      //錯誤用法:線程不安全,各個線程先啓動自身事務,再競爭鎖

}

內部類

補充:

1 局部內部類可使用外部方法的不可變的局部變量的引用(final修飾 或 實際不變的)。這裏的不可變指的是基本變量或引用變量不變,引用變量對應的Object的狀態是能夠改變的 。相似於閉包效果。

 

多線程 

Q 線程安全三要素

https://blog.csdn.net/shanchahua123456/article/details/79463844

Q 下列哪一個是原子操做?

A: j++    B: j = 1   C: j = j   D: j = j+1

答案:B ,其餘是有取值/計算/賦值 的複合操做,是線程不安全的

Q 線程一、2同時執行,有沒有可能打印出「haha」?

//僞代碼
x=1;
y=1;

public void a(){  //線程1 執行
     x=5;
     y=6;
}

public void b(){  //線程2 執行
    if (y==6)&&(x<>5){
       print("haha");
    }
}

答案:有可能,由於jvm重排序問題。如有須要請遵照happenbefore原則,保證可見性和有序性

 

Q voliatile關鍵字的做用,以及happensbefore原則

https://blog.csdn.net/shanchahua123456/article/details/79463199

voliatile+cas 可以保證線程安全的三大要素。

 

Q ThreadLocal<T>

https://blog.csdn.net/shanchahua123456/article/details/79464141

適合作線程級變量,起到前程隔離做用

 

Q 控制多個線程順序執行

 方法1:CountDownLatch或其餘AQS組件   方法2 :join()  方法3:經過一個volitiale/Atomic標誌變量 

 

Q 線程JOIN,程序輸出?

Thread t1=new Thread(()->{try {
		Thread.sleep(5000);
		System.out.print(Thread.currentThread().getName());
} catch (InterruptedException e) {
			e.printStackTrace();
}},"T1");
	  
t1.start();  
t1.join();
System.out.print(Thread.currentThread().getName());

答案:T1 main ,主線程等待join線程執行完後再執行,若t1.join()時t1已經結束,則主線程繼續執行

 

Q 併發執行時,在無鎖非同步狀況下,不要以相似 i==0 的判斷做爲參照(除非i只有0/1兩態)。最好改爲i<=0 這種,由於高併發狀況下,可能瞬間變爲負值

 

Q 線程狀態與相互轉換

圖中Resource blocked,Object waiting狀態,線程會進入阻塞掛起狀態,並放棄當前阻塞條件對應的已持有資源(好比鎖),等待其餘線程喚醒本身。

Runnable 是線程狀態正常能夠運行,可是當前未得到CPU時間片

Time waiting至關於sleep(),此時線程進入睡眠狀態,並會主動醒來,且過程當中不釋放已持有資源。 

 

Q 線程掛起和喚醒?

掛起線程自身不能喚醒本身,喚醒線程A必須由其餘線程完成。線程掛起後會放棄鎖等資源。等被喚醒後再去嘗試獲取鎖。

且其餘線程要持有使線程A掛起的那個對象的引用。(能夠經過傳參或是公共變量、公共容器的方式,使其餘線程得到喚醒因子)

簡單理解就是用哪一個Object掛起線程A,線程B就要用那個Object的喚醒方法來喚醒線程A。

使線程A掛起方式有不少,線程A可調用以下方法, 好比 objectA.wait()、conditionA.await()、CountDownLatch.await() 、lock()等待持鎖 、AQS對象.acquire()等等。(AQS能夠本身實現同步組件,可是首先考慮使用JDK提供的已完成組件)

線程B必須得到使線程A掛起的對應對象的引用(objectA,conditionA,AQS對象等),並調用其喚醒方法例如 objectA.notify(),AQS對象.release()。

例外還能夠強制喚醒。中斷線程,而且經過拋出InterruptedException來喚醒它;若是線程遇到了IO阻塞或synchronized,無能爲力。

 

Q 什麼致使線程阻塞、放棄時間片

阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),學過操做系統的同窗對它必定已經很熟悉了。Java 提供了大量方法來支持阻塞,下面讓咱們逐一分析。

調用Thread.sleep,Thread.yield,Thread.suspend都不會釋放已持有資源(好比同步監視器鎖)

obj.wait()只會釋放當前線程已經持有的此obj對象的鏡像鎖syn(obj)

lock.condition.await()釋放condition對應的Lock。操做lock.condition必須先持有lock

 

方法 說明
sleep() sleep() 容許 指定以毫秒爲單位的一段時間做爲參數,它使得線程在指定的時間內進入阻塞狀態,不能獲得CPU 時間,指定的時間一過,線程從新進入可執行狀態。 典型地,sleep() 被用在等待某個資源就緒的情形:測試發現條件不知足後,讓線程阻塞一段時間後從新測試,直到條件知足爲止
suspend() 和 resume() 兩個方法配套使用,suspend()使得線程進入阻塞狀態,而且不會自動恢復,必須其對應的resume() 被調用,才能使得線程從新進入可執行狀態。典型地,suspend() 和 resume() 被用在等待另外一個線程產生的結果的情形:測試發現結果尚未產生後,讓線程阻塞,另外一個線程產生告終果後,調用 resume() 使其恢復。
yield() yield() 使當前線程放棄當前已經分得的CPU 時間,但不使當前線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另外一個線程。讓同優先級的線程有執行的機會
wait() 和 notify()

調用obj的wait(), notify()方法前,必須得到obj鎖。兩個方法配套使用,wait() 使得線程進入阻塞狀態,它有兩種形式,一種容許 指定以毫秒爲單位的一段時間做爲參數,另外一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程從新進入可執行狀態,後者則必須對應的 notify() 被調用。

wait, nofity和nofityAll這些方法不放在Thread類當中,緣由是JAVA提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。因此把他們定義在Object類中由於鎖屬於對象

Lock() 等待鎖的線程出現阻塞,線程釋放鎖後觸發喚醒後續等待線程
AQS組件

線程狀態控制器

https://blog.csdn.net/shanchahua123456/article/details/82780252

 

Q.爲何wait()方法和notify()/notifyAll()方法要在同步塊中被調用

https://blog.csdn.net/JAVAziliao/article/details/90448820

這是JDK強制的,避免出現lost wake up問題,wait()方法和notify()/notifyAll()方法在調用前都必須先得到對象的SYN鎖。objcet.wait()至關於對應的此objcet鏡像鎖的條件Condition對象。

操做obj.wait()、obj.notify()必須先持有syn(obj)鎖。

同理操做lock.condition.await()/signal()等也要先取得對應的lock,不然彙報IllegalMonitorStateException。

 

Q 線程wait()必定要保證在notify以前,否則線程會一直掛起。

如下代碼存在什麼隱患? 指望效果是main線程等待sub線程執行完任務後喚醒本身

//錯誤代碼(有可能sub線程先執行notify,致使main一直wait)
Thread main= Thread.currentThread();
Thread sub=new Thread(()->{main.notify()});  //sub線程持有main對象
sub.start();
main.wait();  //main.wait();對應main Object對象的鏡像鎖

答案:能夠用CountDownLatch代替,把同一個CountDownLatch交給sub線程用來喚醒main線程。由於CountDownLatch若是已減到0後,即便後執行CountDownLatch.wait()也不會掛起當前線程。 countDown()確保要被執行最好放在finally中,避免等待線程永不喚醒。線程不能喚醒本身

 

Q condition.await()線程被喚醒後怎樣確保條件狀態?

答案:最好在while循環中判斷條件,保證線程醒來後,第一時間判斷條件。

volitiale int i; 
while (i<0){ //也能夠限制重試次數,超出拋出異常,避免死循環
      condition.await();
}

 

Q 釋放/喚醒  等操做在finally中執行

  最好將釋放/喚醒等操做放在finally中,避免由於異常沒法執行,致使另外一個線程永久掛起

--下面代碼存在風險,如有一個線程put時拋出異常,end.await()永遠不會被喚醒
      CountDownLatch end = new CountDownLatch(100);

        for (int i=0;i<100;i++){
             int a=i;
             threadPoolExecutor.execute(() -> {
                
                 Cache.put(a); //PUT方法可能拋出RuntimeException
                 
                 end.countDown();
             });
         }
        end.await();

 

Q  異步任務利用Future提早暴露結果提升並行工做效率,避免重複執行

主線A將異步任務提交到線程池後返回Future,將Future放入共享緩存,線程A繼續執行後續工做。其餘線程先判斷緩存中有無Future。已存在則直接future.get()等待結果。

 

Q 併發環境,避免重複執行更新緩存,防止緩存擊穿

假設場景中有一個成員變量List  result是DB查詢結果做爲本地緩存,result是懶加載當執行查詢方法時判斷null則查詢DB結果賦值result,其餘前程都直接訪問緩存。怎麼防止緩存擊穿保證只有一個線程完成DB查詢,其餘線程都訪問緩存數據。

分析:使用LOCK雖然也能防止擊穿,可是全部執行到stop的線程都要串行化持鎖,即便第一個持鎖線程已經更新了緩存。形成後續線程排隊讀緩存,影響執行效率。

//result本地緩存
//下面代碼雖然可以避免擊穿,可是效率低
if (result==null){
     //stop 停此處線程都須要串行化取鎖,效率下降
     //實際上只要一個線程更新緩存後,其餘線程就能夠併發讀緩存
     synchronized (this){  
        //二次檢查      
        result=getCache();
        if (result==null){
          //查詢DB更新緩存  
        }  
        return  result;      
     }
}
return  result;

方法一:CAS自旋雙重檢查鎖

其特色是非阻塞,不會像Lock同樣將線程掛起。比較經典的例子:ConcurrentHashMap中initTable()方法。

下面是一個很是簡單的例子。AtomicLong 做爲非阻塞鎖,利用CAS的原子性搶鎖,保證同時只有一個線程能進入CAS保護的代碼塊。可是線程搶成功進入CAS保護代碼塊後,要先作二次檢查判斷(result==null)。由於可能出現線程2在stop位置丟失cpu時間片,此時線程1成功進入CAS更新result後又把getlock設置爲0。等線程2取得CPU再次運行時其仍然能夠進入CAS塊,這時若不作二次檢查會形成result重複更新。

--下面最簡易思路
for{
查詢緩存
沒有則AtomicLong.CAS取得DB查詢權利?
獲權:二次檢查緩存  (存在)?是返回:否 ->查詢DB->更新緩存->放權->CountDowbLatch喚醒
無權:CountDowbLatch等待喚醒->查緩存
}


List result;
AtomicLong getlock; //==1 當前有線程加載result

List getList(){

for(;;){
   if (result!=null){
         return result;
   }

   //stop
   if (getlock.compareAndSet(0, 1)){//cas保證線程安全三要素
      //取得更新權利
      if (result==null){    
         //雙重檢查鎖         
         try{
            result=mapper.get();
            return result;
         }finally{
            //釋放更新權利
            getlock.set(0);         
         }          
      }
   }
  
  //未得到權利的線程繼續FOR循環 直到取得結果
  //若不想讓這些線程一直FOR循環,減小CPU消耗。可使用countDowbLatch.await()將其阻塞。等待更新result的線程將其喚醒
  Thread.yield();
}

}

方法2: AtomicReference<Future<List>> result 提早暴露結果,用CAS判斷result中引用是否爲null,作爲競爭更新權利的鎖。

 

Q  Lock與Cas+for自旋鎖

Lock會阻塞線程是線程掛起等待喚醒,Cas自旋鎖不會。可是在超高併發鎖的競爭十分激烈時Lock可能效果更好,由於掛起的線程不會佔用CPU。因此鎖的選擇要在作充分的場景測試後再作取捨。

也能夠CAS+CountDowbLatch使線程進入掛起狀態減小CPU消耗。

 

Q 下面哪一個行爲被中斷不會致使InterruptedException?

Thread.join 、Thread.sleep、Object.wait、CyclicBarrier.await、Thread.suspend、reentrantLock.lockInterruptibly()

答案:Thread.suspend 。thread1.suspend()與thread1.resume()必須成對出現。

suspend()方法就是將一個線程掛起(暫停),resume()方法就是將一個掛起線程復活繼續執。

行當執行thread1.interrupt()中斷方法後,會將thread1設置爲中斷狀態。可是並非真的中斷,須要在本身的業務代碼中主動判斷當前線程狀態:終止代碼或拋出異常。

若thread1阻塞在Thread.join Thread.sleep、Object.wait、CyclicBarrier.await、reentrantLock.lockInterruptibly()等狀況下,thread1會從阻塞中喚醒並拋出InterruptedException,。

可是thread1此時處於IO阻塞、synchronized阻塞、reentrantLock.lock()阻塞、Thread.suspend()狀態下時,interrupt()中斷對其沒有做用,thread1會一直阻塞、等待外界喚醒。

 

Q 同步轉異步、異步轉同步思想?

同步轉異步:將同步串行化的代碼,提交到線程池/MQ等等,讓同步串行化執行變成異步並行執行,能夠縮短任務總體的用時。

異步轉同步:例如線程A將一部分任務交給線程B執行,可是最終線程A須要等待B的執行結果。此時須要藉助JDK的同步組件例如:Future、AQS組件、CountDownLatch等使線程A掛起,線程B執行完成後,要經過當時使線程A掛起的同步組件對象引用釋放資源,喚醒線程A。

https://blog.csdn.net/shanchahua123456/article/details/85933937

https://blog.csdn.net/shanchahua123456/article/details/86744402


Q 假設以下代碼中,若t1線程在t2線程啓動以前已經完成啓動。代碼的輸出是

public static void main(String[]args)throws Exception {
    final Object obj = new Object();
    Thread t1 = new Thread() {
        public void run() {
            synchronized (obj) {
                try {
                    obj.wait();
                    System.out.println("Thread 1 wake up.");
                } catch (InterruptedException e) {
                }
            }
        }
    };
    t1.start();
    Thread.sleep(1000);
    Thread t2 = new Thread() {
        public void run() {
            synchronized (obj) {
                obj.notifyAll();
                System.out.println("Thread 2 sent notify.");
            }
        }
    };
    t2.start();
}

答案:

Thread 2 sent notify.

Thread 1 wake up

Q.SimpleDateFormat 是線程安全的?

答案:不是,可使用 ThreadLocal<DateFormat>實現線程封閉 或 DateTimeFormatter 。

若是是 JDK8 的應用,可使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。

推薦第三方JAR包JODA-time

Q 併發生產隨機數Math.random()的隱患?

答案: 會因競爭同一seed 致使的性能降低 , 推薦使用ThreadLocalRandom

Q 線程之間信息傳遞,線程上下文的實現和隱患?

1 經過公共對象/變量傳遞進行信息交互,注意線程安全的控制

2 有線程上下文的設計思想,可經過ThreadLocal實現,實現線程安全封閉+同線程各個方法間傳遞。

   可是線程、進程之間切換、調動,注意上下文內容的傳遞。

   例如:線程A將任務提交到線程池,線程池中的線程與線程A的線程上下文(ThreadLocal)是不一樣的。

  或是微服務之間的調用,也涉及到線程切換的問題。能夠將線程A的上下文做爲參數或httpheader等手段傳入消費線程。消費線程能夠直接使用。或能夠經過Aop/攔截器 在消費方法執行前,把上下文參數或httpheader同步到本身的同類型線程上下文中。ThreadLocal注意釋放。

Q 怎麼檢測一個線程是否持有對象監視器

Thread類提供了一個holdsLock(Object obj):boolean方法,當且僅當對象obj的監視器被某條線程持有的時候纔會返回true,注意這是一個static方法,這意味着」某條線程」指的是當前線程。

Q 主線程提交任務到線程池,此任務出現異常,主線程合適能檢測到?

直到操做Future.get()時纔會捕獲任務拋出的異常,不操做Future.get()則主線程正常運行無異常。

public class Test1 {

    @org.junit.Test
    public  void testThread() throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));
        Test1 this1=this;

        Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                this1.say(); //利用局部變量 使局部內部類中拿到外部對象引用
                if(true)
                   throw new RuntimeException("錯誤");
                return "ok";
            }
        } );

        System.out.println("wait....");
        Thread.sleep(1000);
        System.out.println("wait get...");
        System.out.println(future.isDone());
        System.out.println(future.get(1000,TimeUnit.SECONDS));
        System.out.println("get");
    }

    private void say(){
        System.out.println("main say");
    }
}
wait....
main say
wait get...
true

java.util.concurrent.ExecutionException: java.lang.RuntimeException: 錯誤

 

 

Q System.currentTimeMillis()性能問題

https://www.jianshu.com/p/3fbe607600a5

併發調用此函數效率很是低那有什麼策略既能夠獲取系統時間又避免高頻率調用呢。

  • 策略一:若是對時間精確度要求不高的話可使用獨立線程緩存時間戳。
  • 策略二:使用Linux的clock_gettime()方法。
Java VM可使用這個調用而且提供更快的速度currentTimeMillis()。
若是絕對必要,可使用JNI本身實現它.
  • 策略三:使用System.nanoTime()。

 

策略一實現
class MillisecondClock {  

    public static final MillisecondClock CLOCK = new MillisecondClock(10);  
    private long rate = 0;// 頻率  
    private volatile long now = 0;// 當前時間  
   
  
    private MillisecondClock(long rate) {  
        this.rate = rate;  
        this.now = System.currentTimeMillis();  
        start();  
    }  
  
    private void start() {  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                now = System.currentTimeMillis(); 
                try {  
                    Thread.sleep(rate);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }                  
            }  
        },"系統時間統一更新Thread").start();  
    }  
  
    public long now() {  
        return now;  
    }  
      
}


 

反射

Q 程序輸出?

package test;
import java.util.Date; 
public class SuperTest extends Date{ 
    private static final long serialVersionUID = 1L; 
    private void test(){ 
       System.out.println(super.getClass().getName()); 
    } 
      
    public static void main(String[]args){ 
       new SuperTest().test(); 
    } 
}

答案:test.SuperTest

1.首先 super.getClass() 是父類的getClass()方法,其父類是Date,它的getClass()方法是繼承自Object類並且沒有重寫,

因此就是調用object的getClass()方法。返回當前運行時的類。

2.在調用getName()方法而getName()是:包名+類名

Q Class.forName和classloader的區別

class.forName()除了將類的.class文件加載到jvm中以外,還會對類進行解釋,執行類中的static塊。
classLoader只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。

Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。而且只有調用了newInstance()方法採用調用構造函數,建立類的對象
 

JVM

Q.堆棧常量池

public static int INT1 =1 ;
public static int INT2 =1 ;
public static int INT3 =1 ; 

局部變量:

int a1 = 1;
int a2 = 1;
int a3 = 1;



Q 常見的內存泄漏 OOM

http://www.javashuo.com/article/p-ppqsozta-dp.html

https://blog.csdn.net/ZHWang102107/article/details/83247210

http://www.javashuo.com/article/p-sadhbmwv-mv.html

靜態變量,容器,ThreadLocal,連接釋放,非靜態內部類持有外部類引用,外部循環引用,單例(由於自身引用在本類中,本身包括成員變量都沒法釋放)。

及時釋放引用、使用非強引用.....

排查:

http://www.javashuo.com/article/p-nthaviyi-mu.html

相關文章
相關標籤/搜索