跳槽不算频繁, 但参加过不少面试(电话面试, face to face 面试), 面过大 / 小公司, 互联网 / 传统软件公司, 面糊过(眼高手低, 缺乏实战经验, 挂掉), 也面过人, 所幸未因失败而气馁, 在此过程中不断查缺补漏, 养成了踏实, 追本溯源, 持续改进的习惯, 特此将自己经历过, 构思过的一些面试题记录下来, 如果答案有问题, 欢迎拍砖讨论, 希望能对找工作或者感兴趣的同学有所帮助, 陆续整理中.
1. synchronized 和 reentrantlock 异同
相同点
都实现了多线程同步和内存可见性语义
都是可重入锁
不同点
实现机制不同 synchronized 通过 java 对象头锁标记和 Monitor 对象实现 reentrantlock 通过 CAS,ASQ(AbstractQueuedSynchronizer)和 locksupport(用于阻塞和解除阻塞)实现 synchronized 依赖 jvm 内存模型保证包含共享变量的多线程内存可见性 reentrantlock 通过 ASQ 的 volatile state 保证包含共享变量的多线程内存可见性
使用方式不同 synchronized 可以修饰实例方法 (锁住实例对象), 静态方法(锁住类对象), 代码块(显示指定锁对象) reentrantlock 显示调用 trylock()/lock() 方法, 需要在 finally 块中释放锁
功能丰富程度不同 reentrantlock 提供有限时间等候锁 (设置过期时间), 可中断锁(lockInterruptibly),condition(提供 await,signal 等方法) 等丰富语义 reentrantlock 提供公平锁和非公平锁实现 synchronized 不可设置等待时间, 不可被中断(interrupted)
2. concurrenthashmap 为何读不用加锁
jdk1.7
1)HashEntry 中的 key,hash,next 均为 final 型, 只能表头插入, 删除结点
2)HashEntry 类的 value 域被声明为 volatile 型
3)不允许用 null 作为键和值, 当读线程读到某个 HashEntry 的 value 域的值为 null 时, 便知道产生了冲突 -- 发生了重排序现象(put 设置新 value 对象的字节码指令重排序), 需要加锁后重新读入这个 value 值
4)volatile 变量 count 协调读写线程之间的内存可见性, 写操作后修改 count, 读操作先读 count, 根据 happen-before 传递性原则写操作的修改读操作能够看到
jdk1.8
1)Node 的 val 和 next 均为 volatile 型
2)tabAt 和 casTabAt 对应的 unsafe 操作实现了 volatile 语义
3. ContextClassLoader(线程上下文类加载器)的作用
越过类加载器的双亲委派机制去加载类, 如 serviceloader 实现
使用线程上下文类加载器加载类, 要注意保证多个需要通信的线程间的类加载器应该是同一个, 防止因为不同的类加载器导致类型转换异常(ClassCastException)
4. tomcat 类加载机制
来源: http://www.jianshu.com/p/f80709d07662