一、进程与线程
进程: 系统进行资源分配和调度的基本单元
线程: 程序执行的最小单位(新建New、就绪Runnable、阻塞Blocked、等待Waiting、超时等待Timed_Waiting、终止Terminated)
二、wait和sleep区别
1、wait 属于Object的方法,任何实例对象都可以调用;sleep 是Thread的静态方法
2、wait 会释放锁,但调用它的前提是当前线程要占有锁(即代码要在Synchronized中);sleep 不会释放锁,它也不需要占用锁,它抱着锁睡觉
3、两者都可以被中断(interrupted方法)
三、并发、并行和管程以及用户线程和守护线程
并发: 同一时刻,多个线程同时执行同一个资源任务,多个线程对一个点
并行: 同一时刻,多个线程同时执行多个资源任务,之后再汇总
管程: 保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只能有进程被调用(执行线程首先需要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,在执行过程中其他线程无法获取同一个管程。类似锁)
用户线程和守护线程: 当主线程结束,用户线程还在运行,JVM存活;当主线程和用户线程都结束,都是守护线程,JVM结束
四、LOCK接口
1、Synchronized: 修饰代码块,修饰普通方法(锁是当前的实例对象),修饰静态方法(类对象)8锁问题
- 获取锁的线程执行完之后,然后释放锁的占有
- 当线程执行发生异常,JVM会让线程自动释放
- 但是,当线程中存在等待IO或者其他原因(比如调用sleep)被阻塞了,但是又没有释放锁,其他线程就会一直等待,非常影响执行效率 => Lock
2、Lock
- 由ReentrantLock(可重入锁)唯一实现
- 对于等待和通知的方法(原wait和notifyAll),现可以使用newCondition()方法创建Condition对象去实现(现await和signalAll);同时根据标志位,创建多个Condition实例对象去实现精准唤醒
- ReentrantReadWriteLock(读写锁)内置readLock和writeLock,实现更细粒度的锁,同时读读可以并发提高性能,读写和写写都需要等待占有锁被释放才可以进行
- 锁降级: 先获取写锁,再获取读锁,先释放写锁,此时只有读锁,可以进行读操作,于是写锁被降级为读锁(可重入)
3、Lock和Synchronized区别
- Synchronized是Java内置的关键字,而Lock是在java.util.concurrenment.lock包下的
- Synchronized会自动释放锁,而Lock需要手动释放锁,否则会出现死锁,因此需要将释放锁写在try-catch-finnally里面
- Synchronized无法获取锁的状态,而Lock可以判断是否成功获取锁
- Synchronized不能被interrupt终端命令中断,等待的线程会一直等待下去,而Lock可以让等待的线程响应中断
- Lock可以提高多个线程的读操作的效率
五、线程间通信:共享内存和消息传递
1、一个线程加1,一个线程减1(Synchronized和Lock)
2、三个线程轮流顺序打印(A打印5次,B打印10次,C打印15次,循环10次)
六、集合的线程安全问题
多个线程去同时添加ArrayList,造成并发异常修改问题
1、使用Vector 中的add方法被Synchronized同步修饰,因此线程安全,但读效率低
2、使用Collections.synchronizedList(new ArrayList())方法去创建
3、使用JUC包下的CopyOnWriteArrayList(写时复制技术,动态数组volatile+线程安全volatile和互斥锁lock)看源码
多个线程去同时添加HashSet,造成并发异常修改问题
1、HashTable,使用Synchronize修饰,线程安全但效率低
2、使用Collections.synchronizedSet(new ArraySet())方法创建
3、CopyOnWriteHashSetArraySet
七、创建线程的方式
1、继承Thread类
2、继承Runnable接口,重写run方法(获取不到返回结果)
3、继承Callable接口,重写call方法(使用FutureTask接口可以获取到call方法的返回结果)
FutureTask实例对象使用get获取返回结果,get只有在完成计算结果之后获取,同时get只计算一次,若没有完成计算调用get方法会一直被阻塞,直到任务完成为止(FutureTask多用于耗时计算,主线程完成自己的任务后再去获取线程的返回结果)
八、JUC三大辅助类
CountDownLatch计数器(班长锁门) .await()等待执行完子线程的结果后才去执行主线程
CyclicBarrier循环阻塞(七龙珠召集) .await()等待执行完子线程的结果后才去执行主线程
Semaphore信号量(停车位) .acquire()阻塞当前线程 .release()释放当前线程
九、阻塞队列
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add() | offer() | put() | offer(, ,) |
移除 | remove() | poll() | take() | poll(, ,) |
检查 | element() | peek() | / | / |
看官方API文档!!
常见阻塞队列: ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、SynchronousQueue等等
十、线程池
三种创建线程池方式
1、单个线程的线程池:newSingleThreadExecutor
2、固定个数线程的线程池:newFixedThreadPool
3、可缓存线程池:newCachedThreadPool
七大参数
当执行execute()方法时,会先判断线程数,若大于核心线程数则加入队列中进行等待,若超过队列的线程数则新建不超过最大线程数的非核心线程运行这个任务,若还存在线程则将执行拒绝策略(如下)
(AbortPolicy直接抛异常、CallerRunPolicy退回给被调用实例对象、DiscardPolicy默默的不做任何处理丢弃、DiscardOldestPolicy直接丢弃等待时间最长的线程)
1、核心线程数:corePoolSize
2、最大线程数:maximumPoolSize
3、等待超时时间:keepAliveTime
4、超时时间单位:TimeUnit
5、阻塞队列:BlockingQueue
6、线程工厂:ThreadFactory
7、拒绝策略:RejectedExecutionHandler
不允许使用Executors去穿件线程池,防止OOM!!一般通过ThreadPoolExecutor创建
十一、Fork/Join和CompletableFuture
Fork: 将一个大任务拆分成多个小任务
Join: 将拆分任务执行的结果进行合并
RecursiveTask 继承 ForkJoinTask,使用 ForkJoinPool 进行 submit() 提交(需要关闭)
CompletableFuture: 异步(调用)编程,可以使任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态
十二、volatile和CAS
Volatile
1、保证可见性:(两个线程同时从主内存中读取,然后其中一个线程进行修改操作,但是另一个线程的值并未获取到最新值,因此就会出现非预期的效果)
JMM内存模型(八大原子操作):线程从主内存读取锁,先read和load到自己的工作内存中,然后在进行use和assgin,接着将最新结果store和write传送到主内存中,最后释放锁
2、不保证原子性: 多线程执行相加操作(使用原子类定义 AtomicInteger)
3、禁止指令重排: 保证特定的操作执行顺序 内存屏障(因为计算机并不一定按照代码顺序执行的,执行过程可能:源代码->编译器优化的重排->指令并行也可能会重排->内存系统也会重排->执行)
CAS: 比较并交换