volatile与synchronized的区别
volatile是轻量级别的syncchronized。volatile的功能是保证共享变量的“可见性”,当一个线程修改一个共享变量,另外一个线程能读到这个修改的值。但是它并不能保证变量的并发更新是原子行的。
volatile是通过内存屏障(Lock前缀)实现的两个功能:
1)将当前处理器缓存行的数据写回到系统内存
2)这个写回内存的操作会使其他CPU缓存了该内存地址的数据无效
synchronized是重量级锁,通过对对象加锁实现调用的同步,主要表现形式分为以下三种:
1)对于普通同步方法,锁是当前实例对象。
2)对于静态同步方法,锁是当前类的Class对象
3)对于同步方法块,锁是synchonized括号里配置的对象
synchronized在JVM中是基于进入和退出Monitor对象来实现方法同步和代码同步的。代码块同步是使用monitorenter和monitorexit指令。monitorenter指令是在编译后插入同步代码块的开始位置,而monitorexit是插入到方法结束处或异常处,JVM保证每个monitorenter必须有对应的monitorexit与之配对。任何一个对象都有一个monitor与之关联,当且仅有一个monitor被持有后,它将处于锁定状态。
synchronized涉及偏向锁,轻量级锁、重量级锁
锁 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外消耗,性能非常高 | 如果线程存在竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争,会使用自旋会消耗CPU | 追求响应时间,同步块执行速度非常块 |
重量级锁 | 线程竞争不实用自旋,不会消耗CPU | 线程阻塞,响应时间慢 | 追求吞吐量,同步块执行速度较长 |
JMM
什么是JMM
JAVA 内存模型(java memory model)。
内存划分
JMM规定了内存主要划分为主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。
JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。
内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
模型特征
原子性: 被synchronized关键字或其他锁包裹起来的操作也可以认为是原子的。从一个线程观察另外一个线程的时候,看到的都是一个个原子性的操作。
可见性:每个工作线程都有自己的工作内存,所以当某个线程修改完某个变量之后,在其他的线程中,未必能观察到该变量已经被修改。volatile关键字要求被修改之后的变量要求立即更新到主内存,每次使用前从主内存处进行读取。因此volatile可以保证可见性。除了volatile以外,synchronized和final也能实现可见性。synchronized保证unlock之前必须先把变量刷新回主内存。final修饰的字段在构造器中一旦完成初始化,并且构造器没有this逸出,那么其他线程就能看到final字段的值。
有序性: java的有序性跟线程相关。如果在线程内部观察,会发现当前线程的一切操作都是有序的。如果在线程的外部来观察的话,会发现线程的所有操作都是无序的。因为JMM的工作内存和主内存之间存在延迟,而且java会对一些指令进行重新排序。volatile和synchronized可以保证程序的有序性,很多程序员只理解这两个关键字的执行互斥,而没有很好的理解到volatile(内存屏障)和synchronized也能保证指令不进行重排序。
Happen-Before(先行发生规则)
- 程序次序规则(Program Order Rule):在一个线程内,程序的执行规则跟程序的书写规则是一致的,从上往下执行。
- 管程锁定规则(Monitor Lock Rule):一个Unlock的操作肯定先于下一次Lock的操作。这里必须是同一个锁。同理我们可以认为在synchronized同步同一个锁的时候,锁内先行执行的代码,对后续同步该锁的线程来说是完全可见的。
- volatile变量规则(volatile Variable Rule):对同一个volatile的变量,先行发生的写操作,肯定早于后续发生的读操作
- 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的下一个动作
- 线程中止规则(Thread Termination Rule):Thread对象的中止检测(如:Thread.join(),Thread.isAlive()等)操作,必行晚于线程中所有操作
- 线程中断规则(Thread Interruption Rule):对线程的interruption()调用,先于被调用的线程检测中断事件(Thread.interrupted())的发生
- 对象中止规则(Finalizer Rule):一个对象的初始化方法先于一个方法执行Finalizer()方法
- 传递性(Transitivity):如果操作A先于操作B、操作B先于操作C,则操作A先于操作C
quartz 锁超时
quartz锁超时
主要分为任务执行超时、调度节点不可用
1)任务执行超时,如果超过用户定义的超时时间,主动释放锁,
根据用户定义策略,决定是否暂停任务节点任务
2)节点不可用,quartz本身有个心跳机制,如果节点没有在规定时间周期没有更新心跳,主动释放该节点拥有的所有锁,并在此期间不被调度;等故障节点回复心跳,加入调度机器池。
cpu打满
- 使用
jps
或ps -ef|grep java
命令确定想要分析的应用的进程编号 -
top -p 进程ID -H
找出比较高的线程ID - `从中选择占比较高的线程的编号(PID),并将该PID转换为16进制
jstack 进程ID |grep -A 10 线程16进制
线程池四种拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。