2.2 多线程

2 minute read

2.2.1 锁

  • Java对象头和Monitor
    • 对象在内存分为, 对象头、实例数据和对齐填充
    • 对象头实现synchronized, 轻量锁和重量锁在Mark Word结构中定义
    • ObjectMonitor, wait状态的线程加入_WaitSet, 等待锁block状态的线程加入_EntryList
    • monitor对象在于每个对象的Java头, 因此Java任意对象都可以作为锁
  • synchronized代码块底层原理
    • monitorenter 指令, 进入同步方法, monitorenter则通过操作系统的互斥锁实现
    • monitorexit 指令, 退出同步方法
  • synchronized方法底层原理
    • 隐式, 无需通过monitorentermonitorexit字节码指令控制
    • 方法表结构中ACC_SYNCHRONIZED标识
  • Java虚拟机synchronized的优化
    • 偏向锁
      • 线程A获得锁, 锁即进入偏向模式, Mark Word变为偏向锁结构, 线程A再次请求锁, 无需操作即获得锁
    • 轻量级锁
      • 线程交替执行同步块的场景
    • 自旋锁
      • 避免用户态和内核态的切换
    • 锁消除
      • 优化没必要的锁
  • synchronized的可重入性
    • 对相同对象请求锁, 是允许的, 即synchronized的可重入性
  • 线程中断和synchronized
    • 线程中断操作对synchronized不起作用
  • 等待唤醒机制与synchronized
    • notify(), notifyAll(), wait()方法必须在synchronized代码块内或者方法中

2.2.2 CAS (Compare And Swap)[V, E, N]

  • CPU指令对CAS的支持
    • CAS是CPU的原子指令
  • UnSafe指针
    • Unsafe#compareAndSwapObject()
    • Unsafe#compareAndSwapInt()
    • Unsafe#compareAndSwapLong()
  • 并发中的Atomic
    • 原子更新基本类型
      • AtomicBoolean
      • AtomicInteger
      • AtomicLong
    • 原子更新引用
    • 原子更新数组
    • 原子更新属性
    • 自旋锁

2.2.3 wait/notify、sleep/

  • Object#wait()通过监视锁对象完成, 需要先获得monitor的所有权, 通过synchronized关键字完成, Object#notify()同理
  • waitsleep的区别
    • sleep不影响锁的状态, wait释放锁
    • 调用wait之后需要调用notify来获取CPU资源
    • yield让CPU调度, 类似sleep只是不能指定时间
    • join, Thread A调用Thread B#join, A需要等B结束才能执行

2.2.4 ThreadLocal

  • ThreadLocal存放到ThreadLocalMap

2.2.5 线程池实现原理 Executor

  • ThreadPoolExecutor
    • corePoolSize 核心线程数
    • maximumPoolSize 维护最大线程数
    • keepAliveTime 空闲线程存活时间
    • workQueue 任务队列
    • threadFactory 线程工厂
    • handler 拒绝策略
  • 创建规则(corePoolSize最小存活线程数量[除非allowCoreThreadTimeOut为true])
      1. 线程数量小于corePoolSize, 创建线程处理任务
      1. 线程大于corePoolSize并小于maximumPoolSize之间, workQueue未满, 缓存新任务
      1. 线程小于maximumPoolSize且workQueue满了, 创建新线程处理任务
      1. 线程大于maximumPoolSize且workQueue满了, 使用拒绝策略
  • 资源回收
  • 排队策略
  • 拒绝策略
    • AbortPolicy (默认, 直接抛异常)
    • CallerRunsPolicy (只用调用者所在的线程执行任务)
    • DiscardOldestPolicy (丢弃任务队列中最久的任务)
    • DiscardPolicy (丢弃当前任务)
  • Executors
    • ThreadPoolExecutor
      • Executors#newFixedThreadPool(int nThreads) -> new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        • corePoolSize&maximumPoolSize数量均为nThreads
        • keepAliveTime为0, 即空闲即停止
        • LinkedBlockingQueue无界队列, 即不拒绝任务
      • Executors#newCachedThreadPool() -> new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>()), 对短时间任务性能较好
        • corePoolSize为0, maximumPoolSize为无限大
        • keepAliveTime为60S
        • SynchronousQueue, 无存储空间, 无空闲线程则创建新线程
      • Executors#newSingleThreadExecutor() -> new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue)
        • corePoolSize&maximumPoolSize数量均为1, 即创建一条线程处理任务
        • LinkedBlockingQueue无界限队列任务
      • Executors#newScheduledThreadPool(int corePoolSize)
        • corePoolSize核心线程数
    • ScheduledThreadPoolExecutor
      • ScheduledThreadPoolExecutor#scheduleWithFixedDelay, 固定延迟执行
      • ScheduledThreadPoolExecutor#scheduleAtFixedRate, 固定频率执行

2.2.6 Lock & ReentrantLock

  • Locksynchronized的区别
    • Lock提供可中断获取, 超时中断获取
    • Lock提供等待唤醒机制的多条件变量Condition
  • ReentrantLock(重入锁)
    • 使用:
      ReentrantLock reentrantLock = new ReentrantLock();
      try {
          reentrantLock.lock();
        } finally {
          reentrantLock.unlock();// lock几次, 就要unlock几次
      }
    
    • 原理
      • AbstractQueuedSynchronizer,包含Node head同步队列头, Node tail同步队尾, int state 0表示锁未被占用, 1表示锁被占用, Node, 包含Node SHARED = new Node()共享模式, Node EXCLUSIVE = new Node()独占模式, AbstractQueuedSynchronizer提供共享和独占模板给子类实现, 包含tryAcquire()tryAcquireShared()
      • Sync, ReentrantLock的内部类, 继承AbstractQueuedSynchronizer
      • NonfairSync, ReentrantLock的内部类, 非公平锁
      • FairSync, 公平锁
      • ReentrantLock#lock() -> NonfairSync#lock()
        • -> compareAndSetState(0, 1)执行CAS操作, 获取同步状态, ->setExclusiveOwnerThread(Thread.currentThread)设置独占线程为当前线程
        • else -> acquire(1)
          • -> tryAcquire()
            • -> nonfairTryAcquire()
              • compareAndSetState()再次获取同步状态
              • current == getExclusiveOwnerThread() 判断当前线程是否是ownerThread, 重入锁
          • -> acquireQueued(addWaiter(Node#EXCLUSIVE))
            • Node#addWaiter() -> Node#enq(), 将新节点增加到队尾
            • acquireQueued() -> 自旋过程, 满足条件p == head && tryAcquire()并获取同步状态
        • Condition包含await(), signal()
  • Semaphore, 共享锁
    • 使用:

        Semaphore semp = new Semaphore(5);
        try {
          semp.acquire();
        } finally {
          semp.release();
        }
      
  • 读写锁, 读锁不互斥, 写锁与其它均互斥
  • 自旋锁, 未获得锁则不断重试直到拿到锁

2.2.7 volatile

  • Java内存区域
    • 方法区
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器
  • Java内存模型
    • 原子性
    • 重排序
      • 编译器优化重排序, 不改变单线程的语义下, 重排语句的执行顺序
      • 指令并行重排
      • 内存系统的重排
    • 可见性, 在多线程环境下, 重排导致程序轮序执行的问题, 导致可见性问题
    • 有序性 synchronized可解决原子性, 可见性, 有序性问题, volatile可解决可见性, 有序性问题
  • JMM的happens-before原则[辅助保证程序执行的原子性、可见性以及有序性]
  • volatile内存语义
    • 保证volatile修饰的变量修改的新值对所有线程可见
    • 禁止指令重排序

2.2.8 BlockQueue实现原理

  • ArrayBlockingQueue通过ReentrantLockCondition实现

2.2.9 线程的状态

  • New, 新建状态, 还未调用start()
  • Ready, 就绪状态, 调用start()之后或者调用yield
  • Running, 运行状态, 获得CPU资源
  • BLOCKED, 阻塞状态, 等待锁, 进入synchronized方法或者synchronized
  • TIMED Waiting, 超时等待, 调用sleep(long), join(long), wait(long)
  • Waiting, 等待状态, 调用wait(), join()
  • Terminated, 结束状态

图片来自知乎Dawell的答案: 图片来自知乎Dawell的答案

2.2.10 AsyncTask

  • 线程池
// 串行线程池, 处理任务入队
// Executor sDefaultExecutor = new SerialExecutor();默认执行时调用
private static class SerialExecutor implements Executor {
        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

  public static final Executor THREAD_POOL_EXECUTOR;

  // CORE_POOL_SIZE根据CPU核数创建
  // runnable执行线程池
  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                  CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                  sPoolWorkQueue, sThreadFactory);
          threadPoolExecutor.allowCoreThreadTimeOut(true);
          THREAD_POOL_EXECUTOR = threadPoolExecutor;


  // 执行任务
  public final AsyncTask<Params, Progress, Result> execute(Params... params) {
      return executeOnExecutor(sDefaultExecutor, params);
  }

  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
      // 执行任务
      exec.execute(mFuture);
  }

线程

  • 线程和进程的区别
    • 进程是运行中的程序,线程是进程的内部的一个执行序列
    • 进程是资源分配的单元,线程是执行行单元
    • 进程间切换代价大,线程间切换代价小
    • 进程拥有资源多,线程拥有资源少 多个线程共享进程的资源
  • 内核线程
    • 内核线程就是内核得分身, 一个分身可以处理一个特定事情。这在处理异步事件如异步IO时特别有用。内核线程得使用是廉价的, 唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间, 支持多线程的内核叫做多线程内核(MultiThreads kernel)
  • 轻量级线程
    • 轻量级进程(LWP)是建立在内核之上并由内核支持的用户线程, 它是内核线程的高度抽象, 每一个轻量级进程都有一个特定的内核线程关联, 内核线程只能由内核管理并像普通进程一样被调度
  • 用户线程
    • 用户线程是完全建立在用户空间的线程库, 用户线程的创建、调度、同步和销毁全由库函数在用户空间完成, 不需要内核的帮助, 因此这种线程是极其低消耗和高效的