基礎
1. hashmap的實現
HashMap的底層是用hash數組和單向鏈表實現的 ,當調用put方法是,首先計算key的hashcode,定位到合適的數組索引,然後再在該索引上的單向鏈表進行循環遍歷用equals比較key是否存在,如果存在則用新的value覆蓋原值,如果沒有則插入鏈表頭部。HashMap的兩個重要屬性是容量capacity和加載因子loadfactor,默認值分佈爲16和0.75,當容器中的元素個數大於 capacity*loadfactor時,容器會進行擴容resize 爲2n。
2. hashmap 與 hashtable的區別
1、HashMap是非線程安全的,HashTable是線程安全的。
2、HashMap的鍵和值都允許有null值存在,而HashTable則不行。
3、因爲線程安全的問題,HashMap效率比HashTable的要高。
4、Hashtable是同步的,而HashMap不是。因此,HashMap更適合於單線程環境,而Hashtable適合於多線程環境。
一般現在不建議用HashTable, ①是HashTable是遺留類,內部實現很多沒優化和冗餘。②即使在多線程環境下,現在也有同步的ConcurrentHashMap替代,沒有必要因爲是多線程而用HashTable。
多線程
多線程用幾種實現方法。同步有幾種實現方法。靜態同步方法和非靜態有什麼不同。
多線程實現:
(1)繼承Thread類,重寫run函數
(2)實現Runnable接口,重寫run函數
class Thread1 implements Runnable{
public void run( ){
//run裏一般寫一個while(true)循環
System.out.println(“Runnable“);
}
}
public class Test{
public static void main(String[] a){
Thread1 r = new Thread1();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
//繼承Thread
class Thread2 extends Thread{
public void run(){
System.out.println(“extends“);
}
}
public class Test{
public static void main(String[] a){
Thread t = new Thread2();
t.start();
}
}
同步實現:
可採用synchronized關鍵字和reetrantlock類來實現同步
靜態同步方法 vs 非靜態同步方法
所有的非靜態同步方法用的都是同一把鎖——實例對象本身,也就是說如果一個實例對象的非靜態同步方法獲取鎖後,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,可是別的實例對象的非靜態同步方法因爲跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
而所有的靜態同步方法用的也是同一把鎖——類對象本身,一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!
synchronized方法總結
1.方法聲明時使用,放在範圍操作符(public等)之後,返回類型聲明(void等)之前.這時,線程獲得的是成員鎖,即一次只能有一個線程進入該方法,其他線程要想在此時調用該方法,只能排隊等候,當前線程(就是在synchronized方法內部的線程)執行完該方法後,別的線程才能進入.
public synchronized void synMethod() {
//方法體
}
2.對某一代碼塊使用,synchronized後跟括號,括號裏是變量,這樣,一次只有一個線程進入該代碼塊.此時,線程獲得的是成員鎖.
public int synMethod(int a1){
synchronized(a1) {
//一次只能有一個線程進入
}
}
3.synchronized後面括號裏是一對象,此時,線程獲得的是對象鎖.如果線程進入,則得到當前對象鎖,那麼別的線程在該類所有對象上的任何操作都不能進行,例如:
public class MyThread implements Runnable {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "t1");
Thread t2 = new Thread(mt, "t2");
Thread t3 = new Thread(mt, "t3");
Thread t4 = new Thread(mt, "t4");
Thread t5 = new Thread(mt, "t5");
Thread t6 = new Thread(mt, "t6");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName());
}
}
}
4.synchronized後面括號裏是類,此時,線程獲得的是對象鎖.例如:
class ArrayWithLockOrder{
private static long num_locks = 0;
private long lock_order;
private int[] arr;
public ArrayWithLockOrder(int[] a)
{
arr = a;
synchronized(ArrayWithLockOrder.class) {//-----這裏
num_locks++; // 鎖數加 1。
lock_order = num_locks; // 爲此對象實例設置唯一的 lock_order。
}
}
public long lockOrder()
{
return lock_order;
}
public int[] array()
{
return arr;
}
}
class SomeClass implements Runnable
{
public int sumArrays(ArrayWithLockOrder a1,
ArrayWithLockOrder a2)
{
int value = 0;
ArrayWithLockOrder first = a1; // 保留數組引用的一個
ArrayWithLockOrder last = a2; // 本地副本。
int size = a1.array().length;
if (size == a2.array().length)
{
if (a1.lockOrder() > a2.lockOrder()) // 確定並設置對象的鎖定
{ // 順序。
first = a2;
last = a1;
}
synchronized(first) { // 按正確的順序鎖定對象。
synchronized(last) {
int[] arr1 = a1.array();
int[] arr2 = a2.array();
for (int i=0; i<size; i++)
value += arr1[i] + arr2[i];
}
}
}
return value;
}
public void run() {
//
}
}
對於4,如果線程進入,則線程在該類中所有操作不能進行,包括靜態變量和靜態方法,實際上,對於含有靜態方法和靜態變量的代碼塊的同步,我們通常用4來加鎖.
static synchronized vs synchronized
synchronized是對類的當前實例進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊。
那麼static synchronized是要控制類的所有實例的訪問了,static synchronized是限制線程同時訪問jvm中該類的所有實例同時訪問對應的代碼塊。
一個日本作者-結成浩的《java多線程設計模式》有這樣的一個列子:
pulbic class Something(){
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
那麼,加入有Something類的兩個實例a與b,那麼下列組方法何以被1個以上線程同時訪問呢
a. x.isSyncA()與x.isSyncB()
b. x.isSyncA()與y.isSyncA()
c. x.cSyncA()與y.cSyncB()
d. x.isSyncA()與Something.cSyncA()
這裏,很清楚的可以判斷:
a,都是對同一個實例的synchronized域訪問,因此不能被同時訪問
b,是針對不同實例的,因此可以同時被訪問
c,因爲是static synchronized,所以不同實例之間仍然會被限制,相當於Something.isSyncA()與 Something.isSyncB()了,因此不能被同時訪問。
那麼,第d呢?,書上的 答案是可以被同時訪問的,答案理由是synchronzied的是實例方法與synchronzied的類方法由於鎖定(lock)不同的原因。
synchronized 和 lock鎖的區別
但是由於synchronized是在JVM層面實現的,因此係統可以監控鎖的釋放與否,而ReentrantLock使用代碼實現的,系統無法自動釋放鎖,需要在代碼中finally子句中顯式釋放鎖lock.unlock();