object的常见方法:
clone(),equals(),hasCode(),notify(),notifyAll(),toString(),
finalize(),wait()
Vector、ArrayList、LinkedList的区别
结构上:
Vector、ArrayList是一种类似数组的数据结构,更准确的应该说是顺序表。LinkedList则是以链表的方式进行存储。
区别:Vector不能够存储null,而ArrayList可以存储null。
效率上:从底层实现就可以看出,Vector、ArrayList方便查找,不方便插入和删除,而LinkedList则方便插入和删除,不方便查找。
线程安全上:
Vector是线程安全的,而ArrayList和LinkedList是线程不安全的。如果程序本身是线程安全的,也就是说没有多个线程是共享访问的,选择Vector的效率要比ArrayList高。
节省空间上:
ArrayList是填满的时候会自动进行扩充,Vector也是这样,但是ArrayList会自动扩充容器的50%,而Vector则会扩充容器的100%,显然是ArrayList更节省空间。
另外,从图中也可以看出LinkedList还实现了Queue接口,因此也就是具有了Queue接口的方法,比如offer(),peek(),poll()等方法。
sleep()和wait()的方法
之前在多线程中提到过两者的区别。这里希望能更为详细的总结一下
wait()是object的方法,包括在内的notify(),notifyAll()都是object的方法,是所有对象都有的方法。
sleep()是线程类Thread()独有的方法,并且是类方法,无论new()出多少个线程实例,sleep()方法都是被共享的。
sleep()被同步中所调用,这句话的意思就是说调用sleep的线程是拿到对象锁的线程,同样的wait()也是这样,只有拿到对象锁的线程才有资格调用sleep()或者是wait()。
在线程状态切换中,线程调用了sleep(),仍然会持有对象锁,会导致线程睡眠,进入阻塞状态。达到指定睡眠时间后,也就是睡眠结束后尽心可执行状态,重新参与CPU的抢夺。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
wait()会释放对象锁,放入等待队列,也就是等待阻塞。掌握了对象锁的线程在线程结束后调用notify或者是notifyAll()唤醒等待队列中的一个线程,这个线程就从等待阻塞切换到同步阻塞状态,等待当前线程结束。
notify/notifyAll()执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。故,在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出临界区。即不要在notify/notifyAll()后面再写一些耗时的代码。
Synchronized 和Lock
在之前的文章中写过Synchronized,但是对Lock的基本没有提及
Synchronized是jdk1.2以来一直存在的,而Lock是jdk1.5增加的。
至于为什么在Synchronized又新增了Lock,这是因为在资源竞争激烈的条件下,Synchronized会使得性能下降十几倍。
使用了Synchronized的情况下,只有线程释放对象锁后,其他线程才能够继续执行,这也就意味着在高并发情况下,效率会很慢。
synchronized限制:
1. 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。
2.synchronized 块对于锁的获得和释放是在相同的堆栈帧中进行的。但是,确实存在一些更适合使用 非块结构锁定的情况。
在JDK 官方文档中提到:
ReentrantLock是“一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
更重要的一点是synchronized的对象锁是在线程执行结束自动释放,而lock的需要程序员手动释放锁。这也就意味着对象锁的获得和释放都可以由编码 人员自行掌握。
volatile和synchronized的区别
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
HashTable、HashMap,TreeMap,ConcurrentHashMap,ConcurrentSkiplistMap
继承关系上:
HashTable的父类是Dictorinary,而HashMap的父类是AbstractMap,两者都实现了map接口。HashTable遍历通过的是Enumeration,而HashMap则是Iterator实现的。
TreeMap基于红黑树(一种自平衡二叉查找树)实现的,时间复杂度平均能达到O(log n)。
HashMap是基于散列表实现的,时间复杂度平均能达到O(1)。
ConcurrentSkipListMap是基于跳表实现的,时间复杂度平均能达到O(log n)
当数据量增加时,HashMap会引起散列冲突,解决冲突需要多花费一些时间代价,故在f(n)=1向上浮动。
随着数据量的增加,HashMap的时间花费小且稳定,在单线程的环境下比TreeMap和ConcurrentSkipListMap在插入和查找上有很大的优势
(1) TreeMap与HashMap相比较
Ø HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap是最好的选择。
Ø TreeMap取出来的是排序后的键值对。插入、删除需要维护平衡会牺牲一些效率。但如果要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
(2) TreeMap与ConcurrentSkipListMap相比较
Ø Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率。
从概率上保持数据结构的平衡比显示的保持数据结构平衡要简单的多。对于大多数应用,用Skip list要比用树算法相对简单。由于Skip list比较简单,实现起来会比较容易,虽然和平衡树有着相同的时间复杂度(O(logn)),但是skip list的常数项会相对小很多。Skip list在空间上也比较节省。一个节点平均只需要1.333个指针(甚至更少
线程安全:HashTable是线程同步的,key、value都不能为空。而HashMap是线程不同步,允许key、value值为空。在多线程情况下,如果使用HashMap,一定考考虑同步的问题,加锁,或者是Collections.synchronizedMap()来创建线程安全的对象。
哈希值的使用不同:
HashTable
object有hashCode()方法
int hash = key.hashCode();
int index = (hash&0x7FFFFFFF)%table.length;
HashMap
重新计算hashCode,用与代替求模
最后我们要说的是ConcurrentHashMap,ConcurrentHashMap是HashMap线程安全的实现。
一方面HashMap加锁时是对整个对象进行加锁,而ConcurrentHashMap是局部加锁。把整个Map分成如果各Segment,在put或者是get时利用key.hashCode()等一系列操作计算出应该从哪一个Seg中取或者是放入到哪一个Seg中。从而在性能上相对于HashMap中有很大提升。