进程和线程的区别
- 进程独占内存空间,保存各自运行状态,相互间不干扰且可以互相切换,为并发处理任务提供了可能。
- 共享进程的内存资源,相互间切换更快速,支持更细粒度的任务控制,使进程内的子任务得以并发执行。
进程是资源分配的最小单位,线程是CPU调度的最小单位
- 所有与进程相关的资源,都被记录在PCB(进程控制块)中
- 进程是抢占处理机的调度单位;线程属于某个进程,共享其资源
- 线程只由堆栈寄存器、程序技术器和TCB(线程控制块)组成
进程和线程的区别
- 线程不能看做独立应用,而进程可看做独立应用
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
- 线程有自己的堆栈和局部变量,但没有独立的地址空间,多个进程的程序比多线程程序健壮
- 进程的切换比线程的切换开销大,不可以共享变量
java进程和线程的关系
- java对操作系统提供的功能进行封装,包括进程和线程
- 运行一个程序会产生一个进程,进程包含至少一个线程
- 每个进程对应一个jvm实例,多个线程共享jvm里的堆
- java会自动创建主线程,主线程可以创建子线程
线程的状态
- 新建:创建后尚未启动的线程状态
- 运行(Runnable):包含Running(可能正在执行,可能等待CPU分配执行时间)和Ready(在线程池中等待被线程调度选中,获取CPU使用权)
- 无限期等待(Waiting):不会被分配CPU执行时间,需要显示被唤醒。
如下方式会使线程进入无限期等待:- 没有设置Timeout参数的Object.wait() 方法。
- 没有设置Timeout参数的Thread.join() 方法
- LockSupport.park() 方法
- 限期等待:在一定时间后会由系统自动唤醒
- 阻塞:等待获取排它锁
- 结束:已终止线程的状态,线程已经结束执行
sleep和wait的区别
- sleep是Thread类中的native方法,wait是Object中的native方法
- sleep() 方法可以在任何地方使用
- wait() 方法只能在
synchronized
方法或者synchronized
块中使用 - Thread.sleep 只会让出CPU,不会释放已经占有的锁资源
- Object.wait不仅让出CPU,还会释放已经占用的同步资源锁
notify 和 notifyAll 的区别
锁池EntryList:
假设线程A已经拥有某个对象的锁,而其它线程B想要调用这个对象的synchronized方法(或者块),必须获取该对象锁的拥有权,就会被阻塞,进入对象的锁池等待锁的释放。
等待池WaitSet:
假设线程A调用了某个对象的wait()方法,线程A就会释放对象锁,同时线程A就进入该对象的等待池,等待池中的线程不会竞争对象的锁。
notifyAll
会让所有处于等待池中的线程全部进入锁池去竞争获取锁的机会.notify
只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会.
yield()
- 当调用Thread.yield() 函数时,会给线程调度器一个暗示,即当前线程愿意让出CPU,但是线程调度器可能会忽略这个暗时。
- 即使当前线程让出了CPU,仍然会继续抢占CPU。
- 不会让出已经获取的锁
interrupt()
调用Thread.currentThread().interrupt();
,通知线程应该中断了
- 如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出
InterruptedException
异常. - 如果线程处于正常活动的状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
如何中断线程
需要被调用的线程配合中断
3. 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志位就自行停止线程
4. 如果线程处于正常活动状态,那么会将该线程的中断标志设置位true。但线程会正常运行。
锁
互斥锁的特性
互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制。互斥性也称为操作的原子性。
可见性:必须确保在锁被释放之前,对共享变量所作的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应该获取最新共享变量的值)
synchronized
synchronized JDK早期版本
- synchronized属于重量级锁,依赖Mutex Lock实现
- 线程之间的切换需要从用户太转为核心态,开销比较大
synchronized jdk 6优化
synchronized 的四种状态:无锁,偏向锁,轻量级锁,重量级锁
偏向锁 (锁不存在多线程竞争情况下)减少同一个线程获取锁的代价
核心思想:如果一个线程获得了锁,那么锁就进入偏向模式,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程Id等于Mark Word的ThreadID即可,这样就省去大量有关锁申请的操作。
![](/qrcode.jpg)
轻量级锁 偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁的竞争的时候,偏向锁会升级为轻量级锁。场景:线程交替执行同步块
重量级锁 若同一时刻存在多线程竞争,则升级为重量级锁
锁的内存语义
- 当线程释放锁时,java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;
- 当线程获取锁时,java内存模型会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
自旋锁
- 许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得
- 通过让线程执行忙循环等待锁的释放,不让出CPU
- 缺点: 若锁被其它线程长时间占用,会带来许多性能上的开销
锁消除
JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁
锁粗化
避免在循环体中加锁
synchronized 和 ReentrantLock的区别
synchronized
是关键字,ReentrantLock
是类ReentrantLock
可以对获取锁的等待时间进行设置,避免死锁ReentrantLock
可以获取各种锁的信息ReentrantLock
可以灵活的实现多路通知(Condition
)- 机制不同:
synchronized
操作对象头的Mark Word,Lock
调用Unsafe
类的park()
方法
java内存模型(JMM)
java内存模型(Java Memory Model): 本身是一种抽象的概念,并不是真实存在,描述的是一组规则和规范,通过这组规范定义了程序中各个变量的访问方式(包括实例字段,静态字段和构成数组对象的元素)
volatile
jvm提供的轻量级同步机制
- 被volatile修饰的共享变量对所有线程总是可见的
- 防止指令重排序优化
volatile 变量为何立即可见?
当写一个volatile变量时,JMM会把该线程对应工作内存中的共享变量刷新到主内存中;
当读取一个volatile变量时,JMM会把该线程对应的工作内存设置为无效,从主内存中读取最新的值
volatile 如何禁止重排优化
内存屏障(Memory Barrier)
- 保证特定操作的执行顺序
- 保证某些变量的内存可见性
通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化,强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
volatile和synchronized的区别
volatile
本质是告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized
则是锁定当前变量,只有当前线程才可以访问该变量,其它线程需要阻塞等待。volatile
只能使用在变量级别;synchronized
则可以使用在变量,方法和类级别。volatile
只能保证可见性,不保证原子性;synchronized
既保证可见性,又保证原子性volatile
不会造成线程的阻塞;synchronized
可能造成线程的阻塞volatile
可防止指令重排,变量不被编译器优化;synchronized
中的变量可以被编译器优化。
CAS(Compare and Swap)
CAS属于乐观锁,synchronized属于悲观锁
缺点:
- 若循环时间长,则开销很大
- 只能保证一个共享变量的原子操作
- ABA 问题,解决方案:使用
AtomicStampedReference
(内部实际上是加版本)
线程池
ThreadPoolExecutor的构造函数
线程池拒绝策略
新任务提交execute执行后的判断
线程池状态
线程池大小的选择
- 计算机密集型,耗cpu, 一般是cpu核心数+1或cpu核心数*2
- IO密集型,一般是 cpu核心数 / (1-0.9) 或者 cpu核心数 / (1-0.8)
例如:8核处理器, 8 / (1-0.9) = 80 条io线程