java多线程
1.线程分用户线程和守护线程 用户线程全部结束时虚拟机和守护线程也准备即结束 守护线程代表如GC线程
2.synchronized修饰实例方法时表示对实例加锁 其他线程可以访问该实例其他非加锁方法以及静态加锁方法 不可访问实例加锁方法 修饰静态方法表示对class对象加锁 其他线程可以调用非静态加锁方法 不可调用静态加锁方法
3.线程停止使用interrupt但不会立即停止或者return或者通过共享变量判断
4.可重入锁指获得锁的线程可以再次获得锁 因此释放锁的次数应等同于获得锁的次数 同时可以适用于继承对象
5.volatile用于强制放弃线程本地内存而从主存中获得数据 从指令角度解释为何没有原子性 根据内存模型 子线程获得数据第一条指令从主存获得数据 第二条指令设置数据 第三条指令写回数据 volatile修饰符保证read load use以及assign store write连续且顺序执行 即第一条指令与第二条第三条不能连续执行 所以不具有原子性 常用在多线程下循环的单条件判断 重排序特点在new对象时 将分为三步的分配地址-初始化对象-地址指向对象可能改为分配地址-地址指向对象-初始化对象 通过避免重排序的特点避免发生未初始化就被使用
6.wait和notify方法由object对象提供 wait用于将线程置入等待队列 notify随机唤醒等待队列中的一个线程进入就绪队列 这两种方法均需要在synchronized中调用 唤醒的线程并不会立即获得CPU执行wait后续代码 notify方法不释放锁必须该代码块执行完毕后才释放锁 wait状态的线程若被其他线程调用该线程的interrupt方法将抛出异常
7.主线程调用子线程实例join方法阻赛主线程等待子线程结束 调用时释放获得的锁
8.ThreadLocal类用于线程本地存储变量 每个线程有一个ThreadLocal的内部类ThreadLocalMap变量 该变量以ThreadLocal即外部类和变量为键值对保存每个线程私有的变量 因为每个线程可能可能保存多个私有变量 所以以ThreadLocal为key InheritableThreadLocal用于子线程获得父线程的变量 示例如下
9.lock对比synchronized 支持公平锁即保证进入访问等待线程的顺序 传参 锁定与解锁能放在不同的方法里 获得锁时可以被打断或者直接返回结果而不阻赛 配合condition接口实现与synchronized配合wait/notify相同的等待/通知效果 通过多个condition可以实现选择性通知以及顺序通知 ReadWriteLock接口用于对读写锁即共享锁和排它锁的支持
10.wait sleep yield await对比 sleep为线程方法 不释放锁 进入等待队列 yield为线程方法 不释放锁 进入等待队列 wait为object方法 释放锁 进入阻塞队列直到notify方法被其他线程调用 仅能用在synchronized代码块中 await为condition方法 释放锁 进入阻赛队列直到signal方法被其他线程调用 仅能用在lock代码块中
11.lock与monitor lock通过monitor监视器实现即底层通过monitor实现 java中每个对象有一个锁 执行synchronized代码块时必须获得锁即monitor
12.协程 语言层面上的线程 多个协程运行在一个线程上 上下文大小比线程更小 通过主动放弃CPU资源进行上下文切换
13.runnable callable future futureTask对比 runnable与callable作为executors线程池接受任务参数 runnable的run方法没有返回值 callable的call方法返回泛型 因此callable可返回线程处理后的数据 future用于线程池在调用submit方法接受callable参数时返回类型 内部封装返回结构 用于主线程获得子线程返回数据以及判断线程运行状态 futureTask实现了RunnableFuture接口 该接口继承了Runnable和Future接口 因此既可作参数也可作返回值
15.乐观锁包括版本控制策略和CAS策略 版本控制策略通过版本变量确认能否修改当前数据 CAS策略则通过Unsafe类调用 根据预期值与现值比较若相同则更改为设置值 否则不断重试或加锁 乐观锁带来的ABA问题在CAS中通过标志解决 针对CAS仅适用单个变量的缺点采用AtomicReference类将多变量组合为单变量解决 乐观锁适用线程冲突少的情况
17.synchronized修饰代码块通过指令monitorenter和monitorexit表示锁的获取和释放 修饰方法通过ACC_SYNCHRONIZED标识 线程通过获得管程代表获得锁 只有获得管程的线程才可执行加锁代码
18.JUC包中包括基本类型 数组类型 引用类型 对象属性修饰类型4类
19.BlockingQueue接口表示阻塞队列定义了元素插入删除获取的阻塞 超时 异常方法 实现类ArrayBlockingQueue基于数组实现 大小不能改变 支持公平和先进先出 LinkedBlockingQueue采用链表实现 其他与ArrayBlockingQueue相同 PriorityBlockingQueue相比ArrayBlockingQueue支持优先级与自动扩容 BlockingDeque接口继承BlockingQueue接口 表示双端队列 ConcurrentLinkedQueue类表示非阻赛队列
20.ConcurrentMap接口表示线程安全的map集合 实现类为ConcurrentHashMap ConcurrentNavigableMap接口继承ConcurrentMap接口 用于map子集的操作
21.重入锁用于解决子类继承父类的加锁方法当调用父类方法时产生死锁的问题
22.对于服务器程序 无论开发还是测试都应运行在jvm的server模式下 不仅由于server相比client模式会进行更多优化 同时避免优化带来可能的问题
23.构造函数应注意隐式的this逸出 由于this逸出会导致对象尚未构造完成就被其他线程引用可能导致线程安全问题 如下图所示 如果内部类在回调函数中就调用了外部类的属性 此时由于外部类尚未完全初始完毕 就会导致线程安全问题
24.线程封闭包括Ad-hoc线程封闭即类变量的线程安全通过类的方法来保证 栈封闭则通过在方法内部使用局部变量维护线程安全 ThreadLocal类
25.通过将由于多个原子类组合操作导致的线程不安全转换为将多个原子类封装成一个不可变类实现线程安全 可以避免为了线程安全而加锁的开销
26.常见安全发布模式包括 静态初始化函数初始一个对象引用 将对象引用保存到volatile修饰的域或者AtomicReference对象或正确构造对象的final域或一个加锁的域
27.A类为了实现额外功能而采用组合的方式即使对额外功能加锁依然不能线程安全 因为加锁为A类这与原来类的加锁方法的对象不同导致线程不安全 而若采用继承的方式一方面实现线程安全分布两个不同类中另一方面由于父类存在变更的可能性一旦变更则不能保证线程安全 而在客户类同步则代码更脆弱 因此通常解决方法为通过组合和继承或实现的方式 将所有操作委托给原类同时进行同步
28.多线程访问容器即使快速失败依然还是抛出异常 因此在多线程下迭代访问容器可以采用克隆容器的办法
29.写时复制表示读取线程均读取同对象但写线程复制对象得到副本并修改副本当修改完毕后再将指向原对象的引用改为指向新对象
30.ConcurrentHashMap替代同步需求的map CopyOnWriteArrayList替代同步需求的list CopyOnWriteArraySet替代同步需求的set
31.闭锁用于在某线程运行到某段程序前其他进程执行到一定地方后停止直到该线程运行到后所有线程才可继续运行 代表类CountDownLatch
32.尽量将变量声明为final 尽量将数据和同步机制封装在对象中 可变变量通常情况下需要加锁 当有多个变量要保护时注意使用同一个锁 复合操作应持有锁 在设计过程中注意线程安全问题并以文档形式加以说明
34.java中没有提供抢占式停止线程的功能 因此取消线程要求线程运行任务在逻辑上可中断同时在阻塞方法前判断中断条件并处理抛出的中断异常 以下示例表明在固定时间内返回任务结果否则超时处理异常
35.一些不可中断阻塞例如inputstream的read方法等 可通过关闭的方法取消任务 因此针对不同的不可中断阻塞种类 应该根据不同的解决办法将其继承Callable等类封装cancel方法使得对外提供统一的取消方法
36.针对复合操作且操作为阻塞特点例如根据是否已关闭向缓冲区写入数据 由于写入操作当满时为阻塞操作 此时若判断且写入操作采用加锁操作则会严重影响性能问题 所以采用计数器表示缓存大小同时对计数器的操作进行加锁来解决
37.threadFactory参数用于实现自定义线程 可配合UncaughtExceptionHandler处理未检查异常出现导致线程池内的线程终止的情况
38.任务排队方式包括有界 无界 同步 其中同步队列的原理是必须有消费者在接受 而生产者则直接将任务发布给消费者
39.有界队列满时触发饱和策略 包括中止 抛弃 抛弃最旧的 调用者运行 中止策略扔出异常 抛弃策略不扔出异常 抛弃最旧的则抛弃下个将要执行的任务并重新提交任务 调用者运行策略则要求提交线程来运行任务
40.线程转储包含运行中的线程的栈追踪信息 其中包括锁信息 而内置锁synchornized与线程的栈帧有关 而lock锁仅与线程有关 意味着内置锁可以获得更多信息
41.开放调用表示调用某方法时不需要持有锁 例如将锁的范围从方法缩小到代码块
42.多线程环境下使用对象池导致同步的性能损耗比新建对象的性能损耗更高 所以多线程环境下使用新建对象替代使用对象池
43.优先使用notifyAll于notify 因为可能存在多条件队列 例如某线程wait单实例而另外线程wait多个实例包括前实例 如果只使用notify可能导致错误的条件队列里的线程被唤醒
44.非阻塞算法相对于锁通常有更好的性能 通过非阻塞算法CAS可以实现非阻塞栈 非阻塞队列等数据结构 注意非阻塞链接队列算法实现思路 具体实现为ConcurrentLinkedQueue类
47.sleep(0)当等待队列中有优先级大于等于当前线程的其他线程时选择对应线程运行否则保持该线程运行 sleep(1)引发上下文切换使该线程休眠一段时间由于时间短具体时长由系统时间精度确定
线程池ThreadPoolExecutor
使用线程池原因 避免频繁创建和销毁线程带来的开销 避免因创建线程等待的时间 可对线程进行统一管理
线程池参数及作用 int类型corePoolSize 表示核心线程数 int类型maximumPoolSize 表示最大线程数 long类型keepAliveTime 表示空闲线程存活时长 TimeUnit类型unit BlockingQueue<Runnable>类型workQueue 表示任务队列 ThreadFactory类型threadFactory 用于创建自定义线程的线程工厂 RejectedExecutionHandler类型handler 表示饱和策略
属性 ctl属性是一个AtomicInterger类 前3位表示runState运行状态 后29位表示workerCount有效线程数量 类型为HashSet<Worker>的workers属性代表线程集合
状态
状态转换图1.RUNNING 能接受新提交的任务并且也能处理阻塞队列中的任务
2.SHUTDOWN 不再接受新提交的任务但可以继续处理阻塞队列中已保存的任务 在线程池处于RUNNING状态时调用shutdown方法会使线程池进入到该状态
3.STOP 不能接受新任务也不处理队列中的任务且会中断正在处理任务的线程 在线程池处于RUNNING或SHUTDOWN状态时调用shutdownNow方法会使线程池进入到该状态
4.TIDYING 如果所有的任务都已终止了 workerCount为0 线程池进入该状态后会调用 terminated方法进入TERMINATED状态
5.TERMINATED 在terminated方法执行完后进入该状态 默认terminated方法中什么也没有做
线程池种类 均通过Executors类的静态方法获得 在阿里巴巴开发手册中指明不建议使用该种方式而使用具体的ThreadPoolExecutor方法创建 原因为使得调用者明确该线程池的参数及作用避免无限制创建线程等潜在的风险
1.FixedThreadPool 特点为corePoolSize与maximumPoolSize相等 该参数在创建时确定 适用服务器常见场景
2.CachedThreadPool 特点为corePoolSize为0而maximumPoolSize为Integer.MAX_VALUE 适用短期任务多场景
3.SingleThreadExecutor 特点为corePoolSize与maximumPoolSize均为1 适用需要串行执行场景
4.ScheduledThreadPool 特点是执行延时任务 适用延后及定期执行场景
线程池提交任务规则 当任务提交后判断corePoolSize与当前线程数的大小 若corePoolSize更大则直接创建新线程(调用addWorker方法)否则若当前任务队列未满则添加任务到队列 若任务队列已满则当前线程数与maximumPoolSize比较 若不超过则创建线程 否则调用对应的饱和策略
流程相关类及方法
1.Worker类 继承AQS及实现Runnable方法 代表一个线程 主要属性有firstTask代表第一个任务 在tryAcquire方法中通过CAS(预期0修改1)获得锁 因此为不可重入锁 选择不可重入的原因为获得锁代表正在执行任务(shutdown方法会通过tryLock方法判断该线程是否空闲 返回true则代表空闲 会设置中断) 而执行任务时不应该被中断 而如果可重入则在任务中若调用setCorePoolSize方法会中断当前运行的线程 该线程启动后通过getTask方法获得Runnable执行若返回null则线程结束 getTask方法中会判断该线程是否被中断以及当前线程数与核心线程数比较确认是否返回null
2.addWorker方法 内部先通过CAS操作修改ctl值后通过创建Worker类获得mainLock锁加锁后将创建的Worker类加入Workers属性 该方法两个参数分别为Runnable类型的firstTask和boolean类型的core 第一个参数表示第一次运行的任务 若为空表示直接创建线程并运行任务队列中的任务 第二个参数若为true表示与corePoolSize比较否则与maximumPoolSize比较
3.runWorker方法 循环判断先尝试获得firstTask任务 若为空则调用getTask方法获得任务 若依然为空则调用processWorkerExit方法退出 若firstTask不为空则获得后显示设置null 在获得任务后先加锁 判断若线程池处于shutdown及以上状态则打开中断标志否则关闭中断标志 运行任务
4.getTask方法 循环先判断线程池是否处于shutdown或者stop及任务队列是否为空 符合则线程数自减返回null 判断若线程数大于最大线程数或超时等情况则线程尝试自减并返回null失败则continue 尝试从阻塞队列中获取任务并返回
5.processWorkerExit方法 获得mainLock锁从workers移除线程后释放锁 判断若线程池依然运行状态则因为异常原因此时调用addWorker方法
更详细处理过程参考线程池过程
常用方法 execute方法执行无返回任务 submit方法执行有返回任务 shutdown关闭线程池(不接受任务但线程会将任务队列中的任务执行完毕后退出) shutdownnow关闭线程池(不执行任务队列中的任务并且尝试中断当前运行的任务)
BlockingQueue底层实现 put方法调用putLock的lockInterruptibly获得写锁 获得成功后先循环判断是否满(Object数组) 若满则在类型为ObjectCondiction的notFull阻塞释放锁 不满则调用enqueue方法加到链表尾部后对类型为AtomicInteger的count调用getAndIncrement赋给变量c最终释放锁以及判断若c为0则代表原本链表为空调用signalNotEmpty方法去唤醒阻塞在notEmpty上的线程 take方式与put方法类似
Condition底层实现 将调用await的线程释放锁的资源放到等待队列里面,然后当有其他线程唤醒它的时候,从等待队列里面被移除,放到同步队列里面重新获得锁的资源
锁
乐观锁 CompareAndSwap 简称CAS 原理为通过期望值与当前值进行比较若成功则设置新值 由操作系统提供原子CAS指令 存在ABA问题及长时间自旋问题 前者通过AtomicStampedReference类解决后者通过超过一定次数(阿里巴巴开发手册表示至少3次以上)转为加锁解决
线程转储 通过jps和jstack跟踪线程帧栈情况可以发现 使用Lock锁将无法显示目前获得锁的情况而Synchornized可以显示当前获得锁的对象具体类型
同步器AbstractQueuedSynchronizer
概述 AQS通过模板模式对外提供快速构建同步器的类 对外屏蔽等待队列的维护 通过volatile修饰的int类型变量state表示资源同步状态 实现类通常实现tryAcquire-tryRelease或tryAcquireShared-tryReleaseShared方法以及配合setState和getState使用
获取 内部维护一个等待队列 队列的每个节点对应一个线程 获取资源时如获取失败则作为节点加入到队列尾部 自旋判断若当前节点的前节点为头结点则尝试获取资源 若获取资源成功则返回 若不是头结点则将节点在队列中的位置向前转移直至SIGNAL节点 其中丢弃遇到的CANCEL节点 节点转移完毕后阻塞当前线程
释放 修改stat 若state为0 则代表该资源未被占用 唤醒头结点的下一个节点
公平与非公平 以ReentrantLock类为例 state为0表示无线程占用锁 >0则有线程占用锁 公平锁在tryAcquire方法中即使state为0也要判断如果没有线程排队才通过CAS操作尝试获得锁 而非公平锁直接通过CAS操作尝试获得锁
独占和共享 以ReentrantReadWriteLock为例 获得读锁调用lock方法 该方法内部调用tryAcquireShared方法 该方法先获得state变量(int类型 前16位代表目前读锁数量 后16位代表目前写锁数量)通过位移和与操作后获得写锁数量 若写锁数量不为0且该线程不是写锁线程 直接返回-1进入等待队列 否则还要通过readerShouldBlock判断读状态是否要阻塞(读写Node在同一队列 判断条件为head及head后继节点不为空且下一节点是否为Shared节点)在不阻塞的情况下通过CAS操作修改state值 而获得写锁调用lock方法 该方法内部调用tryAcquire方法 该方法获得state判断是否为0(0代表既没有读锁也没有写锁使用)进行后续操作
子类
1.Semaphore信号量类对外通过acquire和release方法修改信号量 用于资源的共享锁
2.CountDownLatch通过构造器设置count值并提供await和countDown方法阻塞线程和减小count值 用于主线程等待多个子线程全部执行完毕后再处理
3.CyclicBarrier通过构造器设置count值并通过await方法阻塞优先执行完毕的线程直至符合数量的线程到达阻塞点
原子类Atomic
类型
1.基本类型 AtomicInteger AtomicBoolean
2.数组类型 AtomicIntegerArray AtomicReferenceArray
3.引用类型 AtomicReference AtomicStampedReference(通过stamp解决ABA问题)
4.对象的属性修改类型 AtomicIntegerFieldUpdater AtomicLongFieldUpdater