1.Java是一種強(qiáng)類型語言
在JAVA中,所有的數(shù)據(jù)類型所占的字節(jié)數(shù)量與平臺無關(guān)。Java沒有任何無符號(unsigned)形式的int、long、short或byte類型。
2.基本類型(8種)
-
整型 - (4種)
類型 大小 取值范圍 int4字節(jié) -2 147 483 648 ~ 2 147 483 647(正好超過20億) short2字節(jié) -32 768 ~ 32 767 long8字節(jié) -9 223 372036 854 775 808 ~ 9 223 372 036 854 775 807 byte1字節(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種)
類型 大小 取值范圍 float4字節(jié) 大約±3.402 823 47E+38F(有效位數(shù)為6 ~ 7位) double8字節(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();
- 調(diào)用靜態(tài)方法forName獲得類名對應(yīng)Class對象
- 利用反射分析類的能力
-
Field、Method、Constructor: 域、方法、構(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方法-
局部內(nèi)部類
image局部內(nèi)部類不能用public或private訪問說明符進(jìn)行聲明,它的作用域被限制在了聲明它的塊中。- 與其他內(nèi)部類相比,局部類還有一個優(yōu)點(diǎn),不僅能夠訪問包含它們的外部類,還可以訪問局部變量,不過那些局部變量必須實事上為final。
-
匿名內(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á)式 -
靜態(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-resourcestry(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ù)組可以按照人們的意愿排列元素的次序。但是,如果想要查看某個指定的元素,卻又忘記了它的位置,就需要訪問所有元素,直到找到為止-
散列表
imageimage例如,某個對象的散列碼是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)行排序,并將其組織成搜索樹。
散列稍微快一些
-
-
鏈接散列集與映射
LinkedHashSet和LinkedHashMap類用來記住插入元素項的順序。imageMap<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)用程序
-
創(chuàng)建jar文件
jar cvf JARFILENAME file1 file2 ....
image -
清單文件
除了類文件、圖像和其他資源外,每個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ù)組的拷貝
CopyOnWriteArrayList和CopyOnWriteArraySet是線程安全的集合所有的修改線程對底層數(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)建線程池:
imageimage -
預(yù)定執(zhí)行
ScheduledExecutorService接口具有為預(yù)定執(zhí)行 ( Scheduled Execution ) 或 重 復(fù) 執(zhí) 行 任
務(wù)而設(shè)計的方法 。 它是一種允許使用線程池機(jī)制的 java. util . Timer 的泛化 。 Executors 類的
newScheduledThreadPool和newSingleThreadScheduledExecutor方法將返回實現(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 的
CompletableFutureeg: 從一個頁面抽取所有鏈接來建立一個爬蟲。
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>
















