四. 线程安全的集合类
4.1 HashTable 和 ConcurrentHashMap的区别
我们常用的ArrayList,LinkedList,HashMap等等这样的类都是线程不安全的,那如果我们在多线程环境下要使用,可能就会出问题;
针对这种情况,标准库里提供了线程安全版本的集合类,据我了解,从早期的线程安全的集合说起,它们是 Vector 和 HashTable:
Vector:
Vector 和 ArrayList 类似,是长度可变的数组,与 ArrayList 不同的是,Vector 是线程安全的,它给几乎所有的 public 方法都加上了 synchronized 关键字。由于加锁导致性能降低,在不需要并发访问同一对象时,这种强制性的同步机制就显得多余,所以现在 Vector 已被弃用;
CopyOnWriteArrayList 和 CopyOnWriteArraySet:
它们是加了写锁的 ArrayList 和 ArraySet,锁住的是整个对象,但读操作可以并发执行;
HashTable:
HashTable 和 HashMap类似,不同点是 HashTable 是线程安全的,它给几乎所有 public 方法都加上了 synchronized 关键字,还有一个不同点是 HashTable 的 K,V 都不能是 null ,但 HashMap 可以,它现在也因为性能原因被弃用了;
HashTable 和 ConcurrentHashMap的区别:
HashTable 是针对整个哈希表加锁,任何的 CURD 操作都可能会触发加锁,也可能有锁竞争;而 ConcurrentHashMap 是针对每个链表进行加锁,每次进行操作,都是针对对应链表进行加锁,操作不同链表就是针对不同的锁对象加锁,此时不会有锁冲突,没有锁冲突,就没有阻塞等待,这样也提升了效率;
4.2 多线程相关面试题
(1)谈谈 volatile关键字的用法?
volatile 能够保证内存可见性, 强制从主内存中读取数据,此时如果有其他线程修改被 volatile 修饰的变量,可以第一时间读取到最新的值;
(2)Java多线程是如何实现数据共享的?
JVM 把内存分成了这几个区域:方法区,堆区,栈区,程序计数器,其中堆区这个内存区域是多个线程之间共享的,只要把某个数据放到堆内存中, 就可以让多个线程都能访问到;
(3)Java线程共有几种状态?状态之间怎么切换的?
- NEW: 安排了工作, 还未开始行动. 新创建的线程, 还没有调用 start 方法时处在这个状态;
- RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. 调用 start 方法之后, 并正在CPU 上运行/在即将准备运行 的状态;
- BLOCKED: 使用 synchronized 的时候, 如果锁被其他线程占用, 就会阻塞等待, 从而进入该状态;
- WAITING: 调用 wait 方法会进入该状态;
- TIMED_WAITING: 调用 sleep 方法或者 wait(超时时间) 会进入该状态;
- TERMINATED: 工作完成了. 当线程 run 方法执行完毕后, 会处于这个状态;
(4)Thread和Runnable的区别和联系?
Thread 类描述了一个线程,Runnable 描述了一个任务,在创建线程的时候需要指定线 程完成的任务, 可以直接重写 Thread 的 run 方法, 也可以使用Runnable 来描述这个任 务;
(5)进程和线程的区别?
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位