synchronized(未完)

补充知识点

synchronized

synchronized的使用方式有两个:
通过对一个对象进行加锁来实现同步:

synchronized(lockObject){
    //代码
}

对一个方法进行synchronized声明,进而对一个方法进行加锁来实现同步:

public synchornized void test(){
    //代码
}

但是无论是对一个对象进行加锁还是对一个方法进行加锁,实际上,都是对对象进行加锁。

也就是说,对于方式2,实际上虚拟机会根据synchronized修饰的是实例方法还是类方法,去取对应的实例对象或者Class对象来进行加锁。

class Test{
	public synchornized void test1(){
    	//代码
	}
	public synchornized void test2(){
	    //代码
	}
}

在早期jdk中,synchronized是一个重量级锁,开销很大,建议少用。但到了jdk1.6之后,该关键字被进行了很多的优化。

CAS

在这里插入图片描述

CAS(compare and swap)。假设有一个数据,有多个线程同时对这个数据执行+1操作,此时就会出现线程安全问题,我们除了加锁外也可以通过CAS操作解决线程安全问题。过程如下图:
在这里插入图片描述

首先第一个线程将数据拿过来在本地内存中做运算,运算完后在写回去,写回去的时候会先进行比较,如果发现数据变了,整个过程就会重新再来一遍,一直到比较完数据发现数据没变为止。期间也可能会出现ABA问题,就是当你写会数据时发现数据相等,但实际上经过了多次更改又改回来了而已,解决方法就是可以加一个版本号,数据每更改一次版本号就加1,最后对版本号也进行一次比较就行。
在这里插入图片描述

但是如果只是普通的比较然后更改的话也是会出问题的,可能在你比较完后就有新的线程插入进来把值更改掉了,但是CAS却不会出现这样的问题,最终实现靠的是汇编语言指令lock cmpxchg,这条指令的意思是在执行cmpxchg指令过程中会上锁,不允许其他CPU访问这块内存,虽然在最后还是加锁了,但是这种锁比操作系统级别的锁效率要高。

用户态和内核态

操作系统内核能访问所有操作指令,而用户态不行,有些操作普通程序要执行必须向操作系统申请。这就在逻辑上将程序运行分为了两种状态,像OS这种属于内核态,普通程序就属于用户态。

在JDK早期,synchronized又叫重量级锁,因为在当时想要加锁,就需要向OS申请一把锁,通过一个用户态到内核态的调用,然后返回去的时候又会从内核态到用户态。也正是因为每次申请锁都要经过OS,都要经过状态的转换,所以就又叫做重量级锁。

对象在内存中的布局

在不同的虚拟机中,对象在内存中的布局是不一样的,在hotspot虚拟机中假设有这么一个类:

class Test{
	public static void main(String[] args){
		T t = new T();
	}
}
class T{
	long m=a
}

当new一个T对象时,它在内存中的布局为:8个字节的markword,是4个字节的类型指针,8个字节的instance data,和4个字节的内容补齐。
在这里插入图片描述
其中markword是记载了锁、GC和hashcode等信息的;类型指针在默认情况下开启压缩,压缩后是4个字节,通过这个指针能找到T.class这个类;instance data大小看实际的实例属性;在hotspot中对象内存是8字节对齐的,当前面内存不够8的整数倍时,会补齐到8的倍数,内容是空的。对象内存就是由以上四部分组成的。

通过工具JOL我们可以获取对象的内存信息,如下图:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
上图可见当我们给对象上了锁之后,markword的信息就改变了。

锁升级过程

不同的锁的markword记录的信息:
在这里插入图片描述
锁升级过程:
在这里插入图片描述
new-普通对象-偏向锁-轻量级锁-重量级锁
当我们普通的new一个对象的时候,创建的是普通对象,一旦给这个对象加上synchronized关键字的时候,它就会升级为偏向锁,然后一旦产生竞争,它就会升级为轻量级锁,轻量级锁也叫做自旋锁、无锁。再竞争再加剧,就会升级为重量级锁。
偏向锁和轻量级都是在用户空间完成的,重量级锁是需要向内核申请的。

偏向锁

在大多数时间情况下,多数的synchronized代码段在运行的时候只有一个线程访问,此时就没有必要涉及竞争机制,哪个线程先来,锁就会偏向先来的线程,这就是偏向锁。

具体的实现:哪个线程来了,就会利用CAS(Compare and Swap)操作把那个线程的线程ID写到markword中,同时修改偏向锁的标志位。

轻量级锁

当有竞争的时候,首先会进行偏向锁撤销,将markword中的线程ID去掉,然后让两个线程进行竞争,会让每个线程在自己的线程栈中生成一个LR(锁记录),两个线程会通过自旋的方式想方设法将LR的指针写到markword中,成功写入的线程就会持有这把锁,另外一个线程则会CAS继续竞争,一直自旋,直到持有锁的线程执行完释放锁资源,它才会持有锁开始执行。

锁撤销具体过程:
1.在一个安全点停止拥有锁的线程。
2.遍历线程栈,如果存在锁记录的话,需要修复锁记录和Markword,使其变成无锁状态。
3.唤醒当前线程,将当前锁升级成轻量级锁。

升级为轻量级锁具体过程:
1.线程在自己的栈桢中创建锁记录 LockRecord。
2.将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。
3.将锁记录中的Owner指针指向锁对象。
4.将锁对象的对象头的MarkWord替换为指向锁记录的指针。

重量级锁

当有线程自旋超过一定次数(默认10次,可通过-XX:PreBlockSpin来进行更改)或自旋线程超过CPU核数一半,就会升级为重量级锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值