- Thread.sleep() 和 Object.wait() 的区别:
sleep是Thread类的方法,wait是Object类的方法
调用Thread.sleep()方法不会释放锁,而调用Object.wait()方法是会释放当前锁
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 - 线程之间的通信: volatile 和 synchronized
- Thread.join() 方法,等到线程终止之后返回
- Java实现多线程的三种方法:
继承Thread类,重写run() 方法
实现Runnable 接口,重写run() 方法
实现Callable 接口,重写call() 方法,new futureTask() 包裹Callable实现类的势力,new Thread(task).start() - Lock接口与Synchronizd 相比一个是接口层面一个是JVM关键字层面,Lock少了隐式释放锁,但多了可操作性、尝试非阻塞获取锁、可中断获取锁和可超时获取锁。
- 队列同步器是实现锁的关键,提供了 getState、setState、compareAndSetState 模板方法,子类通过继承同步器实现它的抽象方法来管理状态
- ReentrantLock 重入锁,Synchronzied 递归隐式重入,ReentrantLock 可以重入,判断获取锁的线程是否当前线程,是则获取锁,锁的释放通过计数器实现,获取锁自增、释放锁自减,等于0时全部成功释放
- 公平锁和非公平锁,公平与否是FIFO原则,非公平情况下,刚释放锁的线程再次获取锁的机会更大,非公平容易造成饥饿但有更好的吞吐量。
- 读写锁,维护一对锁,rwl = new ReentrantLock() rwl.readLock() 和 rwl.writeLock(),只需要读操作时获取读锁写操作时获取写锁即可实现同时允许多个读线程,在写锁获取后其他读、写线程均阻塞。
- 锁降级是针对于读写锁,在持有写锁期间通过重入的特性获取读锁,然后将写锁释放,如果先释放写锁再获取读锁,这样的分段过程不叫锁降级。
- LockSupport 工具可以用来实现线程的阻塞和唤醒,park() 阻塞 unpark() 唤醒
- Condition 接口对应Object 监视器,获取对象的锁:Lock.lock() 获取锁,Lock.newCondition() 获取Condition 对象,Object.wait() = Condition.await(), notify = signal , notifyAll = signalAll
- Java 1.6 ConcurrentHashMap 使用锁分段替代HashTable 的synchronized低效同步,首先有多个Segment 然后每个Segment 又有很多个HashEntry 桶,先通过“再散列”两次散列定位 Segment,再使用散列定位HashEntry , get() 方法不用加锁,因为所有get() 获取的共享变量均声明为volatile保证可见和最新,put 方法需要加锁,一般要判断是否扩容,扩容仅对单个Segment进行, HashEntry 采用table数组+单向链表的数据结构
- Java 1.8 改进一:取消segments字段,直接采用transient volatile HashEntry(K,V) table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。
- 阻塞队列(生产者消费者),队列满时,生产阻塞,等待消费者消费唤醒;队列空时,消费阻塞,等待生产者生产唤醒。Java 中有 7 种阻塞队列,具体不展开。
- Fork/Join 框架,类似MR,Fork 将大任务分成一个个小任务,Join 合并小任务的计算结果得到大任务的结果。
- CountDownLetch 和 Cyclic Barrier 同步屏障,Cyclic Barrier 可以 reset 多次使用,比如当计算发生错误可以重置执行
- Semaphore 信号量,控制并发数,比如当前有30个线程在执行,想控制只允许10个线程并发,就可以用 s = new Semaphore(10),s.acquire() - 并发代码 - s.release()
- 线程池原理:每个线程池设置有 coreSize 和 maxSize ,当一个任务要执行,如果线程数 < coreSize 时创建新线程执行任务,如果 coreSize <= 线程数 < maxSize(),放进阻塞队列,如果阻塞队列已满则创建新线程,如果线程数已经等于 maxSize 并且 阻塞队列已满,则拒绝执行(不同策略,如抛异常、丢弃等)
- 线程池Excutor 执行方法,execute(Runable) 无返回值;Future future = submit(Callable) 有返回
- 线程池关闭方法,shutdown 并不马上真正关闭,等待正在执行的线程任务执行完成;shutdownNow 立即中断正在执行的线程。
- 线程池分两类:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor(时间调度),ThreadPoolExecutor 又分为 FixedThreadPoolExecutor(固定大小的Worker 线程个数)、SigleThreadPoolExecutor(仅有一个Worker线程) 和 CachedThreadPoolExecutor (无界)
- 减少上下文切换的方法:无锁并发编程、CAS算法、减少使用线程、协程
- 原子操作实现,物理机实现方式:LOCK# 总线加锁,缓存一致性缓存加锁。Java 实现:循环CAS、锁(除偏向锁为也均为循环CAS实现)
- 重排序:编译器重排序、指令重排序、内存系统重排序
- 内存屏障:所有处理器均实现了StoreLoad 内存屏障
- volatile 使用Lock#前缀,利用缓存一致性保证内存可见性。保证单个volatile变量读/写的原子性,但对于volatile++ 包含自身的操作,可以拆解为先读、后加、再写入,不保证原子性。
- Synchronzied 对代码块进行同步使用Monitorenter 和 Monitorexit 配对指令,每个对象都有一个Monitor与之关联,对于同步方法使用 ACC SYNCHRONIZED 实现。
- volatile 写相当于释放锁,volatile 读相当于获取锁,所有volatile 和 锁均能实现线程之间通信。
- JMM volatile 语义实现:StoreStore + volatile写 + StoreLoad + volatile读 + LoadStore + LoadLoad,在X86 上 volatile写 + StoreLoad + volatile读
- CAS 实现方式,编译器不会对volatile 和 volatile 读后面任意内存操作重排序,不会对volatile 和 volatile 写前面任意内存操作重排序,CAS 同时拥有 volatile 读和写的特性,即编译器不能对 CAS 和 CAS前面和后面的内存操作从排序,基本的实现方式是判断是否为多处理器环境,如果是在 cmpxchg 指令加上 lock 前缀锁住总线,后来为了减少开销使用缓存锁定。
- concurrent包基本都是定义共享变量为volatile,通过CAS改变共享变量的值,配合volatile读/写 和 CAS所具有的volatile读/写内存语义实现线程之间的通信
- 线程优先级(部分系统没有用),thread.setPriority(10),默认为5,对于偏计算线程设置优先级低,偏IO线程设置优先级高,确保处理器不被独占。