1.Java中的Object类的共有方法
toString(),HashCode(),wait(),notify(),equals(),getClass(),clone(),finalize();
2.Hash 碰撞冲突
产生:对象Hash的前提是实现了equals()和hashCode()两个方法,而HashCode()的作用是保证了对象返回唯一的hash值,但是当两个对象计算值一样时,这样就会发生碰撞冲突。 因此发生hash冲突的前提是hashCode返回相等
解决:
1).开放地址法
2).再哈希法
3).拉链法 (HashMap就是使用此方法,如果hash冲突,那么就通过hashCode()返回的值,找到对应的bucket(哈希桶),然后将对象追加到桶的最后面,即在遍历链表,在最后一个节点追加这个冲突的对象)
4).建立一个公共溢出区
3.HashMap和HashTable的区别
两者几乎完全相像,但主要的区别有:线程安全性,同步(synchronization),以及速度。
HashMap不安全
HashTable安全 因为查询有增删操作中加了锁
4.HashCode 作用,如何重载hashCode方法
1)hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2)如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3)如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用一致,否则就会违反上面提到的第2点;
4)两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。
5.Java代理模式
代理模式:是一种设计模式,目的是希望能做到代码重用。具体的讲:代理设计模式是不是直接访问被代理对象的方式,而是通过代理对象去访问的。
如:商户 --->明星经纪人 ------->明星
房子---->中介------------>房东
分为动态代理和静态代理
我们根据加载被代理类(目标对象)的时机不同,将代理分为静态代理和动态代理。
1)如果在代码编译时能确定被代理的类是哪一个,那么就可以直接使用静态代理。
2)如果在代码编译时不能确定被代理类是哪一个,那么可以使用动态代理。 流行框架有:RPC、Spring下的AOP机制。
动态代理主要用到的一个类和一个接口,分别为Proxy类与InvocationHandler接口
每一个动态代理类都必须要实现InvocationHandler这个接口(代码中的中介) ,内部是通过反射实现的。
InvocationHandler接口中的方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
//proxy: 指代我们所代理的那个真实对象
//method: 指代的是我们所要调用真实对象的某个方法的Method对象
//args: 指代的是调用真实对象某个方法时接受的参数
Proxy类的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
//loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
//interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
//h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
使用方式 Proxy.newProxyInstance(handler.getClass().getClassLoader(), houseOwner
.getClass().getInterfaces(), handler);
6.Java 泛型
泛型:是JDK5中引入的一个新特性,提供了编译时类型安全检查机制,改机制运行程序员在编译时检测到非法的类型。
public <T> void Sort(T[] t) {
Arrays.sort(t);
}
它只会对编译时期检测,运行时,无法检测,亲测List<>中可以反射注入一些其他类型的数据,并且size增加了,也可以list.get()到它
7.Synchronized
常见的三种使用方法
1)普通的同步方法,锁是当前实例对象
2)静态的同步方法,锁是当前类的Class对象,字节码数据存在永久态中,是该类的一个全局锁。
3)对于同步代码块,锁是synchronized括号中配置的对象。
通过反汇编代码可以观察到:
同步代码块是使用MonitorEnter和MoniterExit指令实现的,在编译时,MonitorEnter指令被插入到同步代码块的开始位置,MoniterExit指令被插入到同步代码块的结束位置和异常位置。
任何对象都有一个Monitor与之关联,当Monitor被持有后将处于锁定状态。MonitorEnter指令会尝试获取Monitor的持有权,即尝试获取锁。
同步方法依赖flags标志ACC_SYNCHRONIZED实现,字节码中没有具体的逻辑,可能需要查看JVM的底层实现(同步方法也可以通过Monitor指令实现)。
ACC_SYNCHRONIZED标志表示方法为同步方法,如果为非静态方法(没有ACC_STATIC标志),使用调用该方法的对象作为锁对象;
如果为静态方法(有ACC_STATIC标志),使用该方法所属的Class类在JVM的内部对象表示Klass作为锁对象。
8.ReentrantLock
这种比较常见大家也都在用,主要是防止资源使用冲突,保证同一时间内只有一个操作可以使用该资源。
但与synchronized的明显区别是性能优势(伴随jvm的优化这个差距在减小)。同时Lock有更灵活的锁定方式,公平锁与不公平锁,而synchronized永远是公平的。
ReentrantLock lock=new ReentrantLock();//默认为false,非公平锁
ReentrantLock lock=new ReentrantLock(true);//公平锁
不公平锁与公平锁的区别:
公平情况下,操作会排一个队按顺序执行,来保证执行顺序。(会消耗更多的时间来排队)
不公平情况下,是无序状态允许插队,jvm会自动计算如何处理更快速来调度插队。(如果不关心顺序,这个速度会更快)
9.乐观锁与悲观锁
1) 悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,
用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
2)乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。
在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,
加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
10.Volatile
JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
I. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内
存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
II. 它会强制将对缓存的修改操作立即写入主存;
III. 如果是写操作,它会导致其他CPU中对应的缓存行无效。
网友评论