java筆記
Main.java文件中的入口類(即包含main方法)的訪問權限不一定非要設定為public,不加public也是可以運行的。但main方法必須是public。
以
/**
開頭*/
結尾的注釋可以自動生成文檔。-
java包含8種基本數據類型:4種整型(byte、short、int、long)、2種浮點型(float(有效位6~7)、double(有效位15))、char、boolean。
字節數 其他 byte: 1 short: 2 int: 4 范圍正好超過20億 long: 8 加L、l后綴 float: 4 加F、f后綴 double:8 char: 2 boolean:
十六進制0x開頭,八進制以0開頭,二進制0b開頭,如0b1001
= 9;
數字之間可以加下劃線,如:123_4567
,0b1000_0011
。
java的整形數據沒有無符號類型(unsigned)。
對浮點型數據,有3個特定值:
Double.POSITVE_INFINITY:正無窮
Double.NEGATIVE_INFINITY:負無窮
Double.NaN:不是一個數字,表示0/0或者負數的平方根的結果。
浮點型不要用 == 比較。應該比較他們的差的絕對值是否小于某一個很小的數。
char類型采用了unicode編碼,unicode編碼單元可以表示為十六進制,其范圍是:\u0000~\uffff。一般char類型的表示方式有3種:
- 使用單引號:'0','a','B'。
- 使用轉義序列符,針對一些特殊字符: \b,\n,\,",'等
- 使用十六進制值,如:\u0035='5',\u0008=\b
局部變量在使用(除了對其賦值操作)之前必須進行顯式的初始化。且變量的聲明盡量靠近第一次使用的地方。
關鍵字final修飾的變量只能被賦值一次,之后就不能再更改了,習慣上常量名使用全大寫。final修飾的類的成員變量,必須保證在初始化函數之后其已經被賦值,否則編譯報錯。全局類常量 public static final
修飾`。
位運算符:&按位與、|按位或、^按位異或、~按位非
<<左移,低位補0;>>右移,高位用符號位填充;>>>右移,高位用0填充
注意對移動運算需要進行模32的操作,如1<<35 = 1<<3
數學函數與常量:Math.sqrt(double)求平方根,冪運算:Math.pow(x,a)求x的a次方。其反函數求對數:Math.exp,Math.log,Math.log10。 常量:Math.PI,Math.E。
數值轉換
分為自動轉換和強制類型轉換。
自動轉換:編譯器不會報警,即轉換后的類型數據范圍大于轉換前的類型。
無信息丟失的轉換:
byte->short->int->long
char->int->double
float->double
精度丟失的轉換:
long->dobule
long->float
int->float
這里int轉float的精度丟失不太理解,如
int n = 123456789;
float f = n;//f is 1.23456792E8
浮點數參考教程:
http://blog.csdn.net/b2b160/article/details/4492519
http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html
http://www.zengdongwu.com/article1.html
對上面的數值進行二元操作時,遵循如下規則:
- 如果2個操作數中有一個是double類型,另一個操作數就會轉換為double類型
- 否則,如果2個操作數中有一個是float類型,另一個操作數就會轉換為float類型
- 否則,如果2個操作數中有一個是long類型,另一個操作數就會轉換為long類型
- 否則,2個操作數都被轉換為int類型
強制類型轉換:
將數據范圍大的類型轉換為數據范圍小的類型
如 double x = 1.66;
int nx = (int)x;
如果想四舍五入的轉換:int nx = (int)Math.round(x)
System.out.println("floor:");//地板 取最小 返回double
System.out.println(Math.floor(5.1));
System.out.println(Math.floor(5.9));
System.out.println(Math.floor(-5.1));
System.out.println(Math.floor(-5.9));
System.out.println("ceil:");//天花板 取最大 返回double
System.out.println(Math.ceil(5.1));
System.out.println(Math.ceil(5.9));
System.out.println(Math.ceil(-5.9));
System.out.println(Math.ceil(-5.1));
System.out.println("round:");//四舍五人,返回long
System.out.println(Math.round(5.1));
System.out.println(Math.round(5.9));
System.out.println(Math.round(-5.1));
System.out.println(Math.round(-5.9));
輸出:
floor:
5.0
5.0
-6.0
-6.0
ceil:
6.0
6.0
-5.0
-5.0
round:
5
6
-5
-6
如果轉換的結果類型超出了目標類型的表示范圍,則會截斷成一個完全不同的值:(byte)300的實際值為44。
不要再boolean與任何類型之間轉換,會發生錯誤,只有極少數才需要將Boolean型轉為數值類型,可以使用 b ? 1 : 0
枚舉類型:
enum Size {SMALL, MEDIUM, LARGE};
Size s = Size.SMALL;
Size類型的變量只存儲這個枚舉類型聲明的某個枚舉值或者null,null表示這個變量沒有設置任何值。
字符串:
字符串就是Unicode字符序列,String類被定義為final類型,表示字符串是不可被修改的,這樣編譯器就可以讓字符串共享,java的設計者認為共享帶來的高效遠遠勝過提取、拼接字符串所帶來的低效率。
子串提取:str.subString(a,b),a表示第一個要提取的字符的位置,b表示第一個不想提取的字符的位置。這樣子串的長度就等于b-a。
代碼String str = "hello";
中str是字符串變量,"hello"是字符串常量。實際上在虛擬機中只有字符串常量是共享的,像+,subString等操作產生的結果是不共享的。
字符串格式化:String.format("%s%d%f%x%c%h%b",....);
一些輸出格式標志:
0:數字前補0 %02x,對10 輸出 0a
#: 添加前綴0x(十六進制)或0(八進制),或者包含小數點。
輸出日期:System.out.printf("%1$tF %1$tT", new Date());
結果:2016-06-04 19:38:13
,其中t標示日期格式化開始,F表示日期格式,2016-06-04;T標示時間格式19:38:13。1$是參數索引,標示將該格式應用在第幾個參數上,以$結尾。索引參數是從1開始,不是0。
System.out.printf("%1$x %1$02x %1$#x %1$#X %n",11);
System.out.printf("%1$tF %1$tT \n", new Date());
輸出
b 0b 0xb 0XB
2016-08-19 16:09:10
可以使用Scanner,PrintWriter對文件進行讀寫操作。
new Scanner(new File("pathname"))
、new PrintWriter(new File(pathname))
常見String面試題:
- String s = new String("abc");創建了幾個String對象。
- String s1 = "abc";
- String s2 = "a";
- String s3 = s2 + "bc";
- String s4 = "a" + "bc";
- String s5 = s3.intern();
請問s1==s3是true還是false,s1==s4是false還是true。s1==s5呢?
1.創建了2個String對象,一個是字符串字面常數,在字符串池中。一個是new出來的字符串對象,在堆中。
s3 = s2 + "bc";相當于
s3 = new StringBuilder().append(s2).append("bc").toString();
此題注意兩點,因為s2是一個變量,所以s3是運行時才能計算出來的字符串,是new的,在堆中不在字符串池中。s4是通過常量表達式計算出來的,他等同于字符串字面常數,在字符串池中。所以,s1!=s3,s1==s4。再看s5,s5是s3放到字符串池里面返回的對像,所以s1==s5。這里新手要注意的是,s3.intern()方法,是返回字符串在池中的引用,并不會改變s3這個變量的引用,就是s3還是指向堆中的那個"abc",并沒有因調用了intern()方法而改變,實際上也不可能改變。
Java語言規范第三版,第3.10.5小節,String Literals的最后一段如下:
- Literal strings within the same class (§8) in the same package (§7) represent references to the same String object (§4.3.1).
- Literal strings within different classes in the same package represent references to the same String object.
- Literal strings within different classes in different packages likewise represent references to the same String object.
- Strings computed by constant expressions (§15.28) are computed at compile time and then treated as if they were literals.
- Strings computed by concatenation at run time are newly created and therefore distinct.
- The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.
首先解釋下什么是字符串字面常數(String Literals),字面常數(Literals)就是你寫在源代碼里面的值,比如說int i = 6; 6就是一個整數形字面常數。String s = "abc"; “abc”就是一個字符串字面常數。Java中,所有的字符串字面常數都放在上文提到的字符串池里面,是可以共享的,就是說,String s1 = "abc"; String s2 = "abc"; s1,s2都引用的同一個字符串對象,而且這個對象在字符串池里面,因此s1==s2。另外,字符串字面常數是什么時候實例化并放到字符串池里面去的呢?答案是Load Class的時候。
前面三句基本廢話,像繞口令一樣,意思就是說任何類任何包,值相同的字符串字面常數(String Literals)都引用同一個對象。
第四句是說,通過常量表達式(constant expressions)計算出來的字符串,也算字符串字面常數,就是說他們也在字符串池中。什么才算常量表達式?總結就是,那就是編譯時能確定的,就算,運行時才能確定的,就不算。以下為例子
- String s = 1 + "23";//算,符合第二條Casts to primitive types and casts to type String
- String s = (2 > 1) + "" ;//算,意味著s=="true",且這個“true”已經放到字符串池里面去了。
- String s = (o instanceof Object) + "";//不算,instanceof這個操作符決定了不算。s=="true",但這個"true"對象在堆中。
留意下面的情況。
final String s2 = "a";
String s3 = s2 + "bc";//算
注意現在的s2+"bc"也算一個常量表達式,理由是s2是一個常量變量(constant variables),問題又來了,什么是常量變量?規范里也說了,被final修飾,并且通過常量表達式初始化的變量,就是常量變量。變量s2被final修飾,他通過常量表達式"a"初始化,所以s2是一個常量變量,所以s3引用的"abc",也在字符串池里面。
下面這句話不是很明白:
再舉個反例:
final String s2 = getA();//s2不是常量變量,但是s2引用的"a"其實還是在常量池中,這兩點不矛盾
public String getA(){return "a"}
String s3 = s2 + "bc";//此時s3不算常量表達式,因為s2不是常量變量
這是時候的s2,就不是常量變量了哦,因為getA()不是一個常量表達式。
另外char中的代碼點和代碼單元的概念還沒弄懂。
大數值類:
BigInteger:add、subtract、multiply、divide、mod、compareTo、valueOf。
BigDecimal:同BigInteger,除了沒有mod方法,進行除法時候,需要設定舍入方式,RoundingMOde.HALF_UP就是四舍五入。
數組創建以后,數字元素初始化為0,boolean為false,對象為null。
可以初始化:int[] arr = {1,2,3};
匿名數組 new int[] {1,2,3};
java允許數組長度為0,即new eleType[0]
使用好數組的工具Arrays類中的方法,如toString,sort,asList,equal,fill,copyOf,binarySearch。
要想快速打印二維數組,可以Arrays.deepToString([][])
類之間的關系
依賴(uses-a)UML:---->
聚合(has-a) UML:-> 又稱關聯
繼承(is-a) UML -△
http://blog.csdn.net/zhengzhb/article/details/7190158
http://blog.csdn.net/sfdev/article/details/3906243
- 依賴的耦合度最弱,表現為局部變量、方法的形參。
- 關聯一般用成員變量來實現,有時也用方法形參實現。關聯可以是雙向的,也可以是單向的。
- 聚合是一種比較強的關聯關系,一般使用成員變量來標識,對象之間存在整體與部分的關系。
- 組合是一種比聚合關系強的關聯,它要求普通的聚合關系中代表整體的對象負責代表部分對象的生命周期,組合關系是不能共享的。為了表示組合關系,常常會使用構造方法來達到初始化的目的。
使用final修飾實例域,則必須確保在構造函數執行后這個域的值被設置。
final修飾符大都應用于基本類型或者不可變的域(類中的每個方法都不會改變其對象,如String)。
final Date date;
表示date這個引用不能指向別的實例對象,但date指向的對象是可以改變的。
類中域會初始化成默認值,但為了代碼的可讀性,盡量進行顯示地進行初始化。
如果自定義一個類沒有提供構造器,那么會使用系統的無參構造器,如果自己至少寫了一個構造器,那么系統就不會提供默認的無參構造器了。
在構造器的第一行使用this(...)
,則會調用當前類的另一構造器,如果是super(...)
則會調用父類的構造器。且子類在執行構造函數時,一定要先對父類進行構造,在構造器的第一行,或者是顯示調用super(...)
中的一個,或是隱式調用super()
,即父類的無參構造函數,前提是父類要有這種無參的構造函數。
類的初始化過程:
在類第一次加載的時候,首先會對靜態域初始化,如果沒有顯示地指定值,則默認的初始值是0,false,null。所有的靜態初始化語句以及靜態初始化塊都將按照它們在類中出現的順序依次進行初始化。這種靜態初始化只會執行一次。
構造器的初始化過程:
- 所有的數據域被初始化成默認值
- 按照在類中聲明出現的次序,依次執行所有的初始化語句和初始化塊
- 如果構造器的第一行調用了其他構造器,則執行其他構造器
- 執行這個構造器的主體
初始塊如果要使用某個域,則該初始化塊必須在域的聲明之后
總結對象創建的過程:
- 首次創建對象時(對象聲明不加載class文件),類中的靜態方法/靜態字段首次被訪問時,java解釋器必須先查找類路徑,以定位.class文件;
- 然后載入.class(這將創建一個Class對象),有關靜態初始化的所有動作都會執行。因此,靜態初始化只在Class對象首次加載的時候進行一次。
- 當用new XX()創建對象時,首先在堆上為對象分配足夠的存儲空間。
- 這塊存儲空間會被清0,這就自動地將對象中的所有基本類型數據都設置成了缺省值(對數字來說就是0,對布爾型和字符型也相同),而引用則被設置成了null。
- 按順序執行所有出現于字段定義處的初始化動作(非靜態對象的初始化)。
- 執行構造器。
在類的內部,變量定義的先后順序決定了初始化的順序,即使變量定義散布于方法定義之間,它們仍舊會在任何方法(包括構造器)被調用之前得到初始化。
繼承時,對象的初始化過程
- 主類的超類由高到低按順序初始化靜態成員,無論靜態成員是否為private。
- 主類靜態成員的初始化。
- 主類的超類由高到低進行默認構造方法的調用。注意,在調用每一個超類的默認構造方法前,先進行對此超類進行非靜態成員的初始化。
- 主類非靜態成員的初始化。
- 調用主類的構造方法。
import除了能導入類,還可以靜態導入:
import static java.lang.System.*;
out.println("");
exit(0);
實際上,這種簡寫形式不利于代碼的清晰度。
訪問權限修飾符
java提供了public、protect、private三種權限修飾符,4種訪問權限,從最大權限到最小權限依次是:public、protected、包訪問權限(默認,沒有關鍵字)和private。分別是:
- public訪問權限,即所有的地方均可以訪問。
- protect訪問權限,同一包內和子類可以訪問。
- friendly訪問權限,即default權限,什么修飾符都不加的權限。同一個包內可以訪問。
- private訪問權限,僅在同一類內才可以訪問。
java最多只允許一個java文件中出現一個public類(該類向外提供接口,并與該java文件的名稱完全一致)。
當一個java文件中無一個Public類時,表明其僅供包內使用,對外界不可見!
對于類的訪問權限只能是:public和什么都不加的包訪問權限(但內部類可以是private或protected的);對于類中的成員的訪問權限可以是上述的四種。
一、public修飾的類:
二、無修飾符(默認包訪問權限)的類:
主要區別是對default權限的類,其他包的類根本無法訪問到此類,更不用說訪問其成員了。
其中,對于一個包里面的包訪問權限類A,在其他package的類里面import類A的時候就已經出錯了。所以,其他package普通類是根本就訪問不了類A,其他package的類也成為不了類A的子孫類。
java泛型
1.使用 通過尖括號來定義 <>
泛型類,接口,<>定義在類名后:
class A<T> {
}
class B<T,V> {
}
interface C <T> {
}
泛型方法定義,<>定義在修飾符后,返回值前:
public <T> void fun(T t) {
}
public static <T> T fun2() {
T t = null;
return t;
}
對定義為 class A<T> {……}
的類,如果定義時 A a = new A();
則T代表的類型使用Object來替代。
泛型類繼承:
class A<T> { }
class B extends A { }
class C<T> extends A<T> { }
class D extends A<String> { }
class E extends A<T> { } //編譯錯誤
//T cannot be resolved to a type
對泛型類傳入不同的泛型參數,并不會真正的生成新的類。
List<String> sList = new ArrayList<>();
List<Integer> iList = new ArrayList<>();
//true
System.out.println(sList.getClass() == iList.getClass());
在靜態方法、靜態初始化塊、或者靜態變量不能使用泛型。
instanceof運算符后不能使用泛型運算符
Collection<String> cs = new ArrayList<>();
//Cannot perform instanceof check against parameterized type List<String>.
//Use the form List<?> instead since further generic type information
//will be erased at runtime
//此處編譯錯誤
if (cs instanceof List<String>) {
}
//正確的 使用?通配符
if (cs instanceof List<?>) {
}
注意Round是Shape的子類,但 List<Round>
不是List<Shape>
的子類型。
public static void main(String[] args) {
List<Shape> list = new ArrayList<>();
List<Round> list2 = new ArrayList<>();
test(list);
test(list2);//編譯錯誤
test2(list2);
}
private static void test(List<Shape> list) {}
private static void test2(List<? extends Shape> list) {}
設定通配符的上限:
List<? extends Shape>
,表示可以接收的泛型類型是Shape或者其子類。同時List<? extends Shape>也是所有Shape泛型List的父類。
public void drawAll(List<? extends Shape> shapes) {
for (Shape shape : shapes) {
shape.draw();
}
}
但是
public void drawAll(List<? extends Shape> shapes) {
for (Shape shape : shapes) {
shape.draw();
}
//但是
shapes.add(new Round());//編譯錯誤
shapes.add(new Shape());//編譯錯誤
shapes.add(new Object());//編譯錯誤
shapes.add(null);//編譯正確
}
因為List<? extends Shape>
表示Shape未知的之類,我們無法準確知道這個類型是什么,所以無法將任何對象加到這個集合中。
設定通配符的下限:<? super Shape>表示Shape本身,或者是Shape的父類。即介于Object和Shape之間的類。
public void drawAll(List<? super Shape> shapes) {
for (Object shape : shapes) {
shape.draw();//編譯錯誤 shape沒有該方法
}
//但是
shapes.add(new Round());
shapes.add(new Shape());
shapes.add(new Object());//shape 父類 編譯錯誤
shapes.add(null);//編譯正確
}
因為java的繼承是從上往下,父類在最上面,故extends,子類以及本身表示上限,super,本身及其父類,表示下限。
Java 泛型 <? super T> 中 super 怎么 理解?與 extends 有何不同?
// compile error
// List <? extends Fruit> appList2 = new ArrayList();
// appList2.add(new Fruit());
// appList2.add(new Apple());
// appList2.add(new RedApple());
List <? super Fruit> appList = new ArrayList();
appList.add(new Fruit());
appList.add(new Apple());
appList.add(new RedApple());
首先,泛型的出現時為了安全,所有與泛型相關的異常都應該在編譯期間發現,因此為了泛型的絕對安全,java在設計時做了相關的限制:
List<? extends E>表示該list集合中存放的都是E的子類型(包括E自身),由于E的子類型可能有很多,但是我們存放元素時實際上只能存放其中的一種子類型(這是為了泛型安全,因為其會在編譯期間生成橋接方法<Bridge Methods>
該方法中會出現強制轉換,若出現多種子類型,則會強制轉換失敗),例子如下:
List<? extends Number> list=new ArrayList<Number>();
list.add(4.0);//編譯錯誤
list.add(3);//編譯錯誤
上例中添加的元素類型不止一種,這樣編譯器強制轉換會失敗,為了安全,Java只能將其設計成不能添加元素。
雖然List<? extends E>不能添加元素,但是由于其中的元素都有一個共性--有共同的父類,因此我們在獲取元素時可以將他們統一強制轉換為E類型,我們稱之為get原則。
對于List<? super E>其list中存放的都是E的父類型元素(包括E),我們在向其添加元素時,只能向其添加E的子類型元素(包括E類型),這樣在編譯期間將其強制轉換為E類型時是類型安全的,因此可以添加元素,例子如下:
List<? super Number> list=new ArrayList<Number>();
list.add(2.0);
list.add(3.0);
但是,由于該集合中的元素都是E的父類型(包括E),其中的元素類型眾多,在獲取元素時我們無法判斷是哪一種類型,故設計成不能獲取元素,我們稱之為put原則。
實際上,我們采用extends,super來擴展泛型的目的是為了彌補例如List<E>只能存放一種特定類型數據的不足,將其擴展為List<? extends E> 使其可以接收E的子類型中的任何一種類型元素,這樣使它的使用范圍更廣。
List<? super E>同理。
假設現在有這么一個類的繼承樹,Plant -> Fruit -> Apple -> RedApple。
List<? extends Fruit> appList2的意思,是一個列表,這個列表里面的元素是Fruit的某個子類T,那么在從appList2中取出一個元素時,編譯器會自動把這個元素轉型為T。那么現在假設T是RedApple,很顯然你往這樣一個appList2中add一個Apple類型的元素,取出后轉型為RedApple必然會失敗;同樣的情況如果T是Apple,你add一個Fruit類型的元素,取出后轉型為Apple,也會拋出異常。也就是說,編譯器會把列表中取出的元素轉型為某種類型,但編譯器不確定這種轉型是不是會成功,即在不保證運行時能順利進行,因此就不允許你add任何類型的元素。
再來看看List<? super Fruit> appList,這個列表的元素都是Fruit的父類T,也就是說當你從這個列表中get一個元素時,編譯器會自動加一句轉型為T的代碼。好,現在你往appList中add一個Apple類型的元素,取出時轉型為T,由于T是Apple的父類,向上轉型沒有問題;加一個RedApple類型的元素,也一樣不會有問題。也就是說,只要保證你往appList中添加的元素是Fruit的子類,編譯器就可以保證在轉型為T時不會拋出異常。因此第二種寫法可以過編譯。
為什么List<? extends Fruit>無法add操作呢,假設Fruit下面還有一個Apple子類,而你的List實例是醬紫的:List<? extends Fruit> list = new ArrayList<Apple>(),這一刻你終于知道了泛型的具體類型是Apple類。
這樣還可以add進Fruit嗎?
而List<? super T>為什么又能add進呢,因為你add進的實例一定會是Fruit及其子類型,而你的List實例的實際泛型參數一定是Fruit及其父類,那么肯定是能add進去的。
關于這兩個的使用,假設你是想消費這個List就該用List<? extends T>,因為你可以進行get操作,你可以調用T的接口方法;假設你是想被這個List消費就該用List<? super T>,因為你可以進行add操作。
java正則表達式的轉義字符規則
Java 的正則表達式在匹配點(.) 和斜杠(/),表達式要分別寫作 //. 和 //// ,難看些, 不好理解。幸好還有些人記住了,匹配點(.) 或 {、[、(、?、$、^ 和 * 這些特殊符號要要前加雙斜框,匹配 / 時要用四斜杠。
Java定義了一些用來格式化輸出的特殊字符。Java使用轉義符來表示這些特殊字符,該轉義符由一個反斜線(\)和一個隨后的助記符組成:
回車 '\r'
換行 '\n'
Tab '\t'
換頁 '\f'
退格 '\b'
由于一對單引號和反斜線對于字符表示有特殊的意義,所以您必須用轉義符來表示它們。
單引號 '\'
換碼符 '\\'
雙引號 '\'''
ThreadLocal
ThreadLocal是什么呢?其實ThreadLocal并非是一個線程的本地實現版本,它并不是一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。
線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是Java中一種較為特殊的線程綁定機制,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。
從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的并且 ThreadLocal 實例是可訪問的;在線程消失之后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。
通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM 為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環境常出現的并發訪問問題提供了一種隔離機制。
概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
void set(T value)
將此線程局部變量的當前線程副本中的值設置為指定值。許多應用程序不需要這項功能,它們只依賴于 initialValue() 方法來設置線程局部變量的值。
T get()
返回此線程局部變量的當前線程副本中的值,如果這是線程第一次調用該方法,則創建并初始化此副本。
protected T initialValue()
返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。如果線程先于 get 方法調用 set(T) 方法,則不會在線程中再調用 initialValue 方法。
void remove()
移除此線程局部變量的值。這可能有助于減少線程局部變量的存儲需求。如果再次訪問此線程局部變量,那么在默認情況下它將擁有其 initialValue。
在程序中一般都重寫initialValue方法,以給定一個特定的初始值。
ThreadLocal使用的一般步驟
1、在多線程的類(如ThreadDemo類)中,創建一個ThreadLocal對象threadXxx,用來保存線程間需要隔離處理的對象xxx。
2、在ThreadDemo類中,創建一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離訪問類型的對象,并強制轉換為要應用的類型。
3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數據,這樣可以保證每個線程對應一個數據對象,在任何時刻都操作的是這個對象。
Integer i=0;
ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
new Thread(new Runnable() {
@Override
public void run() {
local.set(100);
while (true) {
int i = local.get();
System.out.println("task-1 "+ i);
i++;
local.set(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
local.set(0);
while (true) {
int i = local.get();
System.out.println("task---2 "+ i);
i++;
local.set(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
另一個例子
public class ThreadLocalDemo implements Runnable {
//創建線程局部變量studentLocal,在后面你會發現用來保存Student對象
private final static ThreadLocal<Student> studentLocal = new ThreadLocal<>();
public static void main(String[] agrs) {
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
}
public void run() {
accessStudent();
}
/**
* 示例業務方法,用來測試
*/
public void accessStudent() {
//獲取當前線程的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
//產生一個隨機數并打印
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
//獲取一個Student對象,并將隨機數年齡插入到對象屬性中
Student student = getStudent();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(500);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
student = getStudent();
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
protected Student getStudent() {
//獲取本地線程變量并強制轉換為Student類型
Student student = studentLocal.get();
//線程首次執行此方法的時候,studentLocal.get()肯定為null
if (student == null) {
System.out.println(Thread.currentThread().getName()+" create");
//創建一個Student對象,并保存到本地線程變量studentLocal中
student = new Student();
studentLocal.set(student);
}
return student;
}
}
class Student {
private int age = 0; //年齡
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。
另外,說ThreadLocal使得各線程能夠保持各自獨立的一個對象,并不是通過ThreadLocal.set()來實現的,而是通過每個線程中的new 對象 的操作來創建的對象,每個線程創建一個,不是什么對象的拷貝或副本。
通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作為map的key來使用的。
如果ThreadLocal.set()進去的東西本來就是多個線程共享的同一個對象,那么多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有并發訪問問題。
wait(),notify()和notifyAll()
wait(),notify()和notifyAll()都是java.lang.Object的方法:
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
使用方法:
To be able to call notify() you need to synchronize on the same object.
synchronized (someObject) {
someObject.wait();
}
/* different thread / object */
synchronized (someObject) {
someObject.notify();
}
要在某個對象上執行wait,notify,先必須鎖定該對象
Use the same object for calling wait() and notify() method; every object has its own lock so calling wait() on object A and notify() on object B will not make any sense.
Use notifyAll instead of notify if you expect that more than one thread will be waiting for a lock.
-
Always call the wait() method in a loop because if multiple threads are waiting for a lock and one of them got the lock and reset the condition, then the other threads need to check the condition after they wake up to see whether they need to wait again or can start processing.
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); obj.wait(); obj.notifyAll(); } public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Object lock = new Object(); synchronized (lock) { obj.wait(); obj.notifyAll(); } }
執行以上代碼,會拋出java.lang.IllegalMonitorStateException的異常。
注意對區塊內的局部變量,只能使用final修飾符,不能使用static或者volatile.
匿名內部類的原理:
匿名類的參考:
http://liuzxc.github.io/blog/java-advance-02/
http://www.cnblogs.com/chenssy/p/3390871.html
public class ThreadPool {
public static void main(String[] args) {
new Bird().fly();
new Bird() {//匿名內部類
String getName() {
return "bird";
};
}.fly();
}
}
class Bird {
String name;
void fly() {
System.out.println(getName() + " fly");
}
String getName() {
return name;
}
}
輸出:
null fly
bird fly
上述匿名內部類的代碼相當于:
內部類可以自由訪問外部類的成員,因為內部類本身就是外部類的成員。但內部類訪問外部類的局部變量時候卻是有限制的,即不能直接修改外部類的局部變量,并要求局部變量是final類型的。