如果多个线程在做同一件事情的时候。
- 原子性 Synchronized, AtomicXXX、Lock、
- 可见性 Synchronized, volatile
- 有序性 Synchronized,volatile
在上面这段代码中,多个线程同时对多个线程添加,count++是属于Java高级语言中的编程指令,而这些指令最终可能会有多条CPU指令来组成,而count++最终会生成3条指令, 通过javap -v xxx.class 查看字节码指令如下。会出现实际结果却是小于等于20000的值。
public incr()V
L0
LINENUMBER 13 L0
ALOAD 0
DUP
GETFIELD com/gupaoedu/pb/Demo.i : I // 访问变量i
ICONST_1 // 将整形常量1放入操作数栈
IADD // 把操作数栈中的常量1出栈并相加,将相加的结
果放入操作数栈
PUTFIELD com/gupaoedu/pb/Demo.i : I // 访问类字段(类变量),复制给Demo.i这个变量
Synchronized保证线程安全
synchronized有三种方式来加锁,不同的修饰类型,代表锁的控制粒度: 1. 修饰实例方法(非静态方法),作用于当前实例对象加锁,进入同步代码前要获得当前实例对象的锁 2. 静态方法,作用于当前类对象Class加锁,进入同步代码前要获得当前类对象的锁 3. 修饰代码块,指定加锁对象(this是当前实例对象),对给定对象加锁,进入同步代码库前要获得给定对象的锁。
获取相同对象的锁会出现锁的互斥
Synchronized底层原理
Synchronized是如何实现锁的,以及锁的信息是存储在哪里?其它线程是如何知道这个对象的锁已经被其它线程持有了,在对象头中有一个锁的标志lock_flag
Synchronized是如何实现锁的,以及锁的信息是存储在哪里?
MarkWord对象头
并且每一个对象都会有一个monitor监视器对象,重量级锁,即synchronized主要通过这个加锁的
synchronized会进行锁的升级,其实主要CAS来修改锁的标志lock_flag,当前线程指针
synchronized锁的升级
Jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁 等技术来减少锁操作的开销。
这么设计的目的,其实是为了减少重量级锁带来的性能开销(线程阻塞,唤醒会导致CPU在不同的线程间频繁切换),尽可能的在无锁状态下解决线程并发问题,其中轻量级锁的底层实现是基于自旋锁,
- 默认情况下是偏向锁是开启状态,偏向的线程ID是0,偏向锁开启会有一个延迟4秒(因为JVM虚拟机自己有一些默认启动的线程,这些线程里面有很多的Synchronized代码,这些Synchronized代码启动的时候就会触发竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁的升级,效率较低)
- 如果有线程去抢占锁,那么他会去判断这个对象的偏向锁是否被其它线程占有,如果没有就会将获取偏向锁,把对象markword的线程ID改为当前抢占锁的线程ID的过程,如果有就会升级为轻量级锁 -------> 偏向锁,只有一个线程获取锁
- 如果有线程竞争(就是第二线程来抢占锁的时候发现这个对象的偏向锁被其它线程占有,这个对象的锁存在竞争),这个时候会撤销偏向锁,升级到轻量级锁,升级到轻量级锁(其实就是自旋锁实现的)会进行锁的自旋,不断重试CAS获取锁,获取锁成功执行同步代码,否则会自旋10次(默认,-XX:PreBlockSpin参数配置,锁自旋主要为了提升性能,由于重量级锁会将线程阻塞,等锁释放了会唤醒阻塞的线程重新参与锁的竞争,用户态到内核态的转换也会耗性能) ------> 轻量级锁,出现线程线程竞争(一个以上的线程)
- 如果竞争加剧,比如有线程超过10次自旋(-XX:PreBlockSpin参数配置),或者自旋线程数超过CPU核心数的一般,在1.6之后,加入了自适应自旋Adapative Self Spinning. JVM会根据上次竞争的情况来自动控制自旋的时间。升级到重量级锁, ------> 重量级锁,出现严重的线程锁的竞争,并发抢占锁
以上锁的升级都会修改对象头MarkWord中的锁的标志和线程指针
CAS
在轻量级锁进行锁的自旋的时候会用到CAS进行修改对象头MarkWord中的锁的标志和线程指针,使用CAS保证原子性
CAS其实就是乐观锁,数据库的乐观锁也是自己加锁的,行锁,表锁
它可以保证在多线程环境下对于一个变量修改的原子性。 CAS的原理很简单,包含三个值当前内存值(V)、预期原来的值(E)以及期待更新的值(N)。
if(V == E) {
V = N;
}
这种肯定是线程不安全,
但是CAS在JVM底层为我们加了Lock锁,来保证我们线程的原子性,中间不会有线程来操作
LOCK_IF_MP()如果是多核,代表有多线程执行,添加LOCK指令
自旋锁的实现
while(true){
// condition()自旋次数
if(cas()) { // true
// 更改,这里只会有一个线程进来修改,CAS底层加Lock锁
V=N;
}
}