Synchronized原理剖析
1为什么wait,notify,notifyall方法设计在object对象上?
2 object.wait与Thread.sleep(time)区别是什么?
3.概述synchronized关键字的原理?
- 锁的应用?
线程的变量值存储在主内存中,每个线程通过拷贝主内存中的变量值来获取该变量值,然后进行相关操作,再把值同步给主内存中的变量值;在一个线程操作下,并不会发生变量值错乱的情况,如果在多个线程的操作下,并发对变量值进行相关操作!就会出现一定的问题! 这里就设置到锁的问题了!进行原子操作;
悲观锁:总是假设每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,标记该数据为独占状态,其他的工作线程无法获取锁,而让线程挂起(阻塞)
例子:数据库的行锁,表锁;
乐观锁:天真的认为每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候回判断一下在此期间别人有没有去更新这个数据;
例子:java并发编程包 java.tyil.concurrent.atomic包原子变量类 redis中setNX API
乐观锁操作:AtomicCAS(jdk1.5版本以后) :cas(compareAndSwap有三值:预期值,内存中值,替换值; 内存中的值和预期值做比较,一致则替换成替换值,不一样就不操作)+自旋(循坏cas的操作)
Synchronized:
修饰方法内==synchronized(this) 锁的是当前的对象
修饰方法上==synchronized(x.class)锁的是当前类
修饰代码块==锁的对象必须是继承Object
不同的使用方式,代表不同的锁粒度;
前面也已经说啦:线程的变量值存储在主内存中;这个变量值很多情况下都是一个引用对象;
Java里的对象实例在主内存中的布局:
- 对象头(markOop,klassOop)2.对象实际数据(对象里的属性)3.对齐填充(可能存在,对象的大小是8kb的一倍 不足 就回去做填充)
Java里的实例对象在jvm是怎要操作布局的?
对象实例在jvm启动的时候,通过类加载器加载成字节码文件存放在方法区,在new这个实例的时候,通过对象头里的指针(klassOop)指向这个字节码文件,来调用其内的一些属性及方法;
对象头里的markOop 里存储的数据会随着锁标志位的变化而变化;所以判断某个引用对象是否加锁,就看引用对象头的markOop的存储数据是什么;所以synchronized(对象); 括号里的数据不能是某个类型 如一个int类型的数据 它必须是个引用类型且继承Object;
在jdk6版本以前 synchronized的实现是重量级锁(线程阻塞)来实现的 代价很高;
在jdk6以后,加入了针对不同的使用场景的锁机制
无锁—》偏向锁(少量线程)-》轻量级锁(存在多个竞争的线程)-》重量级锁
无锁:没有加锁;对象头里有hashcode,对象分代年龄数据和锁的标志
偏向锁:只有少量线程去访问;对象头(markOop)里数据有个锁标志位为01:(为偏向锁) 还有线程ID值,还有一些基础数据(有hashcode,对象分代年龄数据);再访问该同步代码块的时候,对象头记录的是否为偏向锁,是否为统一个线程id值;不需要是该线程挂起,去拿锁的过程;
轻量级锁:轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁挣用的时候,若第一个线程并没有释放锁或则前偏向锁线程不再存活但又未设置可以重新偏向的设置,则偏向锁就会升级为轻量级锁;
为轻量级锁时:一个线程访问同步块时;线程栈会开辟一个空间分成两部分,一部分替换存储对象头里的基础数据(hashcode,对象分代年龄);对象头就会不再保存这些基础数据了;只有锁标志位和记录指向栈中的信息了;另一部分(owner)里的值指向这个对象(Object)。而这个对象头(markOop)里一部分空间里记录锁标志位为00(轻量级锁) 另一部分空间记录了指向栈中锁记录的指针(就是指向线程栈中,存储原对象头基础数据的区域);这些操作成功;说明这个线程拿到了这个轻量级锁;而另一个线程就会重复这个操作;然这个操作,肯定不会成功!那么这个线程就会自旋(重复这个操作,肯定有时间限制,这个时间限制就是线程获取锁执行代码块后的一个平均值,这个操作是消耗cpu的),如果这样都不能获取这个对象锁,那么这个锁机制就会升级成重量级锁;
重量级锁:对象头(markOop)所记录的数据也就会发生改变;1部分记录了锁标志位(是否为重量级锁)另一部分记录了指向互斥量(重量级锁,ObjectMoNItor)的指针;而ObjectMonitor内存里记录了那些数据呢;
ObjectMonitor里有:1.Entrylist同步等待锁的队列2.Owner当前获取锁的执行线程3.wait set等待集合
运行步骤:当很多线程运行,都会去抢锁;都抢不到的时候,这些线程都是放在Entrylist这个同步等待锁的队列里面去等待;抢到锁的线程,就会到Owner这块;去执行它的代码块;然后释放锁;如果该线程调用了wait方法,这个线程就会进入等待状态,且会放到wait set等待集合里面去,等待被唤醒(进入线程等待队列里去抢锁,执行下去);而且也会释放锁;让Entrylist同步等待锁的队列里的线程去抢这个锁,然后重复这个操作!如果在Owner这块;线程调用的是sleep这个方法;那么这个线程会被睡眠,睡眠到指定的时间,在执行下去;在这段睡眠时间里,锁是没有被释放的,其他线程是抢不到锁的;这里要区分wait方法和sleep方法的区别;
那么这里就可以解释为什么wait,notify,notifyall方法设计在object对象上?
wait,notify,notifyall方法设计在object对象上这是对象内存布局息息相关的;是与锁息息相关的,是jvm机制息息相关的;在object里才有ObjectMonitor这个对象;ObjectMonitor有个wait set等待集合,保存着对象上执行过wait函数这些等待队列;只有对象调用notify来唤醒这些等待队列;进Entrylist同步等待锁的队列里去;去抢到锁,执行下去;所以说为什么wait,notify,notifyall方法设计在object对象上;
2 object.wait与Thread.sleep(time)区别是什么?
3.概述synchronized关键字的原理?
这两个问题 前面也说啦;