对比分析HashMap、LinkedHashMap、TreeMap

 

HashMap的原理 :
简单地说,HashMap 在底层将 key-value 当成一个总体进行处理,这个总体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存全部的 key-value 对,当须要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当须要取出一个Entry时,
也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

简单来讲,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,若是定位到的数组位置不含链表
(当前entry的next指向null),那么对于查找,添加等操做很快,仅需一次寻址便可;若是定位到的数组包含链表,对于添加操做,其时间复杂度依然为O(1),
由于最新的Entry会插入链表头部,急须要简单改变引用链便可,而对于查找操做来说,此时就须要遍历链表,而后经过key对象的equals方法逐一比对查找。
因此,性能考虑,HashMap中的链表出现越少,性能才会越好。

简单地说,HashMap 在底层将 key-value 当成一个总体进行处理,这个总体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存全部的 
key-value 对,当须要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;
当须要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

HashMap的底层经过位桶实现,位桶里面存的是链表(1.7之前)或者红黑树(有序,1.8开始) ,其实就是数组加链表(或者红黑树)的格式,
经过判断hashCode定位位桶中的下标,经过equals定位目标值在链表中的位置,因此若是你使用的key使用可变类(非final修饰的类),
那么你在自定义hashCode和equals的时候必定要注意要知足:若是两个对象equals那么必定要hashCode相同,若是是hashCode相同的话不必定要求equals!
因此通常来讲不要自定义hashCode和equls,推荐使用不可变类对象作key,好比Integer、String等等。java

 

ConcurrentHashMap的原理 : 
ConcurrentHashMap的锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的缘由,是由于全部访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,
每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不一样数据段的数据时,线程间就不会存在锁竞争,从而能够有效的提升并发访问效率,
这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,
其余段的数据也能被其余线程访问。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,
HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 
一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素, 每一个Segment守护者一个HashEntry数组里的元素,
当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。算法

Sleep和wait() :
sleep 是线程类(Thread)的方法,致使此线程暂停执行指定时间,给执行机会给其余线程,可是监控状态依然保持,到时后会自动恢复,调用sleep 不会释放对象锁。因为没有释放对象锁,因此不能调用里面的同步方法。编程

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留必定时间给其余线程执行的机会;
sleep()是Thread类的Static(静态)的方法;所以他不能改变对象的机锁,因此当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,可是对象的机锁并木有被释放,其余线程没法访问这个对象(即便睡着也持有对象锁)。
在sleep()休眠时间期满后,该线程不必定会当即执行,这是由于其它线程可能正在运行并且没有被调度为放弃执行,除非此线程具备更高的优先级。数组

wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还须要返还对象锁);能够调用里面的同步方法,其余线程能够访问;
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronized block中,不然会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。多线程


sleep必须捕获异常,而wait,notify和notifyAll不须要捕获异常并发

sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待必定的时间以后,自动醒来进入到可运行状态,不会立刻进入运行状态,由于线程调度机制恢复线程的运行也须要时间,一个线程对象调用了sleep方法以后,并不会释放他所持有的全部对象锁,因此也就不会影响其余进程对象的运行。但在sleep的过程当中过程当中有可能被其余对象调用它的interrupt(),产生InterruptedException异常,若是你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,若是你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及之后的代码。函数

注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,经过t.sleep()让t对象进入sleep,这样的作法是错误的,它只会是使当前线程被sleep 而不是t线程高并发

wait属于Object的成员方法,一旦一个对象调用了wait方法,必需要采用notify()和notifyAll()方法唤醒该进程;若是线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的全部同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也一样会在wait的过程当中有可能被其余对象调用interrupt()方法而产生性能

三 
这二者的施加者是有本质区别的. 
sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定.比如如说,我要作的事情是 "点火->烧水->煮面",而当我点完火以后我不当即烧水,我要休息一段时间再烧.对于运行的主动权是由个人流程来控制.this

而wait(),首先,这是由某个肯定的对象来调用的,将这个对象理解成一个传话的人,当这我的在某个线程里面说"暂停!",也是 thisOBJ.wait(),这里的暂停是阻塞,仍是"点火->烧水->煮饭",thisOBJ就比如一个监督个人人站在我旁边,原本该线 程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但正个流程并无结束,我一直想去煮饭,但还没被容许, 直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就能够煮饭了,这个被暂停的线程就会从暂停处 继续执行.


其实二者均可以让线程暂停一段时间,可是本质的区别是一个线程的运行状态控制,一个是线程之间的通信的问题

在java.lang.Thread类中,提供了sleep(),
而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操做线程
sleep()能够将一个线程睡眠,参数能够指定一个时间。
而wait()能够将一个线程挂起,直到超时或者该线程被唤醒。
wait有两种形式wait()和wait(milliseconds).
sleep和wait的区别有:
1,这两个方法来自不一样的类分别是Thread和Object
2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其余线程可使用同步控制块或者方法。
3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep能够在
任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4,sleep必须捕获异常,而wait,notify和notifyAll不须要捕获异常

一、Java都有哪些锁?

公平锁/非公平锁
可重入锁
独享锁/共享锁
互斥锁/读写锁
乐观锁/悲观锁
分段锁
偏向锁/轻量级锁/重量级锁
自旋锁
Java实现锁有两种语法,一种是synchronized语句,另一种是reentrantlock关键字。上面是不少锁的名词,这些分类并非全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每一个锁的名词进行必定的解释。

公平锁/非公平锁

公平锁指多个线程按照申请锁的顺序得到锁。

非公平锁指多个线程得到锁的顺序不按照申请顺序。

Java reentranthlock经过构造函数来指定锁是公平仍是非公平,默认是非公平锁,对于synchronized而言,也是一种非公平锁。

非公平锁优势在于吞吐量比公平锁大。

可重入锁

可重入锁又叫递归锁,是指同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。举例以下:

synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
Java reentrantlock是一个可重入锁。(上面的代码就是一个可重入锁的一个特色,若是不是可重入锁的话,setB可能不会被当前线程执行,可能形成死锁)

Synchronized也是一个可重入锁。

可重入锁的优势是能够必定程度避免死锁。

独享锁/共享锁

顾名思义,独享锁是指该锁一次只能被一个线程所持有,共享锁能够被多个线程所持有。

Java reentrantlock是一个独享锁,可是对于lock的另外一个实现readwritelock,其读锁是一个共享锁,写锁是一个独享锁。

对于synchronized是一个独享锁。

互斥锁/读写锁

上边说的独享锁和共享锁是一种广义的说法,互斥锁和读写锁就是具体实现。

互斥锁在Java中具体实现就是reentrantlock。

读写锁在Java中的具体实现就是readwritelock。

乐观锁/悲观锁

乐观锁和悲观锁不是指具体的锁类型,而是对于看待并发编程中加锁问题的角度。

悲观锁认为,对于一个数据的并发操做,必定会改变数据,即便实际上数据没被改变,可是也悲观的认为被改变的可能性比较大,必定要加锁,不加锁迟早要出问题。

乐观锁认为,对于一个数据的并发操做,是不会改变数据的,不加锁也不会出问题。

乐观锁指java中的无所编程,适合读操做很是多的场景。

悲观锁就是指java中,适合并发下写很是多的场景。

自旋锁

在java中,自旋锁是指常识获取锁的线程不会当即阻塞,而是采用循环的方式去尝试获取锁,当循环条件被其余线程改变时,才能进入临界区。
这样的好处是减小线程上下文切换的消耗,缺点是会消耗CPU。

2. HashMap的工做原理?

  • HashMap基于hashing原理,咱们经过put()和get()方法储存和获取对象。当咱们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,而后找到bucket位置来储存值对象。当获取对象时,经过键对象的equals()方法找到正确的键值对,而后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每一个链表节点中储存键值对对象。
  • 当两个不一样的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
相关文章
相关标签/搜索