synchronized
在多线程的环境下,多个线程同时访问共享资源会出现一些问题,而synchronized关键字则保证了多线程操作共享资源时的原子性,可见性,有序性。
原子性:被synchronized修饰的代码块要么全部执行成功,要么全部执行失败。
可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到。执行synchronized时,在unlock之前,会保证共享变量的修改对其他线程可见。
有序性:synchronized关键字可以保证当前只有一个线程拿到锁,访问共享资源,线程对共享资源的操作是串行的。
synchronized实现的锁是什么类型的?
悲观锁:synchronized关键字实现的是悲观锁,每次访问共享资源时都会上锁。
非公平锁:synchronized关键字实现的是非公平锁,即线程获取锁的顺序并不一定是按照线程阻塞的顺序。
可重入锁:synchronized关键字实现的是可重入锁,即已经获取锁的线程可以再次获取锁。
独占锁:synchronized关键字实现的是独占锁,即该锁只能被一个线程所持有,其他线程均被阻塞。
synchronized的底层原理
在jdk1.6之后,为了减少获取锁和释放锁带来的性能损耗,引入偏向锁和轻量级锁。锁的状态会随着竞争的激烈程度升级,这种策略提高了获取锁和释放锁的效率。
偏向锁
引入偏向锁的目的:减少只有一个线程执行同步代码块时的性能消耗,即在没有其他线程竞争的情况下,一个线程获得了锁。
获取偏向锁的流程:
1.检查对象头中Mark Word是否为可偏向状态,如果不是则直接升级为轻量级锁。
2.如果是,判断Mark Work中的线程ID是否指向当前线程,如果是,则执行同步代码块。
3.如果不是,则进行CAS操作竞争锁,如果竞争到锁,则将Mark Work中的线程ID设为当前线程ID,执行同步代码块。
4.如果竞争失败,升级为轻量级锁。
轻量级锁
引入轻量级锁的目的:在多线程交替执行同步代码块时(未发生竞争),避免使用重量级锁带来的性能消耗。但多个线程同时进入临界区(发生竞争)则会使得轻量级锁膨胀为重量级锁。
轻量级锁的获取流程:当在出现了多个线程的竞争,就要升级为轻量级锁(有可能直接从无锁变为轻量级锁,也有可能从偏向锁升级为轻量级锁),轻量级锁的效果就是基于CAS尝试获取锁资源,这里会用到自旋锁,根据上次CAS成功与否,决定这次自旋多少次。
重量级锁
如果到了重量级锁,那就没啥说的了,如果有线程持有锁,其他竞争的,就挂起。
重量级锁底层原理:
当升级为重量级锁的情况下,锁对象的mark word中的指针会指向堆中与锁对象关联的monitor对象。当多个线程同时访问同步代码时,这些线程会先尝试获取当前锁对象对应的monitor的所有权。
Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,代码块同步使用的是monitorenter和 monitorexit 指令实现的,而方法同步是通过Access flags后面的标识来确定该方法是否为同步方法。
ObjectMonitor() {
_header = NULL;
_count = 0; //锁的计数器,获取锁时count数值加1,释放锁时count值减1,直到
_waiters = 0, //等待线程数
_recursions = 0; //锁的重入次数
_object = NULL;
_owner = NULL; //指向持有ObjectMonitor对象的线程地址
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //阻塞在EntryList上的单向线程列表
FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
获取Monitor和释放Monitor的流程如下:
1.当多个线程同时访问同步代码块时,首先会进入到EntryList中,然后通过CAS的方式尝试将Monitor中的owner字段设置为当前线程,同时count加1,若发现之前的owner的值就是指向当前线程的,recursions也需要加1。如果CAS尝试获取锁失败,则进入到EntryList中。
2.当获取锁的线程调用wait()方法,则会将owner设置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒。
3.当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1。当recursions的值为0时,说明线程已经释放了锁。
volatile
可见性
volatile的底层生成的是汇编的lock指令,这个指令会要求线程修改共享变量后强行写入主内存,并通过 CPU 总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。
总线嗅探机制
每个处理器通过监听在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从主内存中把数据读到处理器缓存中。
有序性
单例模式中的懒汉机制中,就存在一个这样的问题。懒汉为了保证线程安全,一般会采用DCL的方式。但是单单用DCL,依然会有几率出现问题。线程可能会拿到初始化一半的对象去操作,极有可能出现NullPointException。(初始化对象三部,开辟空间,初始化内部属性,指针指向引用)
volatile修饰的属性不会出现指令重排的问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。