锁相关的基础知识

1.同步是指当有多个线程并发访问共享资源时,在某一时刻只有一个线程可以访问。
2.阻塞是指当前的线程是挂起还是继续执行。
3.同步的方式有阻塞同步(synchronized和lock)和非阻塞同步(CAS机制)
4.CAS机制需要具有原子性,所以要依赖于硬件,可以把比较和交换通过一条CPU指令来执行。
5.java的unsafe类的某些方法包装了CAS,hotspot对这些特殊方法做了特殊处理,可以把这些方法编译为平台相关的处理器CAS指令。
6.Unsafe在jdk1.9之前,只允许通过启动类加载器加载的类访问,可以通过反射机制访问,在jdk1.9之后,在VarHandle开放了CAS操作。

锁的底层实现

AQS (AbstractQueuedSynchronizer)
1.抽象队列同步器,它是实现锁的基础。
2.AQS的数据结构:volatile state代表加锁的次数(0代表没有现成获得锁) ,CLF队列来保存同步节点。
3.所有的设置节点以及state的操作使用CAS机制
4.是否获得锁的本质是哪个线程通过CAS把state的值从0变为1,state值为0,代表没有线程获得锁,state>0,代表某个线程上了state次锁。\
5.有两种模式:一种是共享(readLock),一种是独占(WriteLock,ReentrantLock)
6.阻塞是通过使用LockSupport这个类来实现的。

ReentrantLock

Sync extends AbstractQueuedSynchronizer
1.通过Sync衍生出了公平锁和非公平锁。
2.1.公平锁和非公平锁的区别是:公平锁是按照等待锁时间的长短来获得锁,非公平锁是不管现在同步队列有没有线程等待,先尝试获得锁。
NonfairSync extends Sync
1.非公平锁的加锁步骤:
	(1)调用lock方法先尝试获得锁(根据CAS把state的值从0变为1,如果成功则获得锁,否则进入步骤(2)执行AQS的acquire方法。
	(2) public final void acquire(int arg) {
			    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
      				  selfInterrupt();  }		
	(3)tryAcquire的逻辑 :先判断当前的state值是否为0,如果为0,则尝试获得锁,否则判断当前的线程和已经获得锁的线程是否一致 ,一致则修改state的值,否则直接返回。
	(4)如果tryAcquire获取锁失败,则执行acquireQueued和addWaiter。
	(5)addWaiter逻辑:把当前线程包装为Node节点通过自旋的方式加入到同步队列中。
	(6)acquireQueued逻辑:判断自己是否是head.next&&尝试获得锁,成功则返回,否则判断自己能否阻塞&&进行阻塞,重复上面的逻辑。
2.非公平锁的释放步骤:
	判断加锁的线程和当前线程是否相同,相同则进行释放(把state值-1),不相同则抛出异常,释放之后如果state的值是0,则唤醒同步队列中等待的第一个线程(继续执行acquireQueued的逻辑)。
FairSync extends Sync
1.公平锁的加锁步骤(和非公平锁的步骤只有第一步和第三部不一样):
	(1)进入步骤(2)执行AQS的acquire方法。
	(3)tryAcquire的逻辑 :先判断当前的state值是否为0,如果为0,则判断同步队列中是否有元素,没有元素则尝试获得锁,如果不为0,否则判断当前的线程和已经获得锁的线程是否一致 ,一致则修改state的值,否则直接返回。
		2.公平锁的释放步骤(和非公平锁的释放步骤一样)

ReentrantReadWtiteLock

1.读锁和写锁使用的是同一个sync对象,都是可重入锁,state的高16位代表上读锁的数量,低16位代表上写锁的数量。
2.写锁(排他锁、独占锁),写锁的上锁和解锁逻辑和ReentrantLock的逻辑相似。
3.读锁(共享锁),读锁最主要的步骤是会在每个线程(ThreadLocal)里保存加读锁的次数。
4.当线程持有读锁时,所有线程都不允许添加写锁,所有的线程都可以添加读锁;当写锁存在时(除了当前线程以外,其它的线程都不允许添加读锁和写锁)。

Condition

1.包含await和signal方法 ,与Object的wait和signal方法类似。
2.lock的阻塞只限于获取锁时候的阻塞,使用condition可以使用自己设定的条件来阻塞线程。
3.会有一个队列来保存Node为condition类型的节点。
4.当条件不满足时可以调用await方法来阻塞当前线程。
5.当条件满足时可以调用signal方法来唤醒队列节点的线程。
6.条件队列和同步队列使用的节点都是一样的,唯一的区别是waitstatus。
7.wait方法的执行逻辑:
	(1)把节点加入到condition队列
	(2)释放拥有的锁
	(3)循环判断是否已经加入同步队列,没有加入则阻塞,加入则执行(4)
	(4)调用cquireQueued(和上面的是同一个方法)
8.signal方法的执行逻辑:
	(1)获取条件队列中的第一个节点。
	(2)修改节点的waitstatus把它加入到同步队列。
	(3)唤醒节点线程。

Synchronized

1.偏向锁、轻量级锁、重量级锁的相关信息保存在对象头的Mark word里。Mark word会根据锁的状态进行复用。
2.进行了改进,增加了自旋锁、偏向锁、轻量级锁、锁之间会进行自动升级。
3.自旋锁:获取不到锁是会进行自旋,看在一定的次数内能否获得锁。
4.偏向锁:会在Mark word里记录线程的id,适用于无并发加锁的情况。当调用hashcode方法时,会进入无锁状态,偏向锁只有在没有计算hashcode的时候才能进入。当出现锁竞争时,会升级为轻量级锁。
5.轻量级锁:会把Mark word复制一份到当前线程的栈中,修改为指向栈记录的指针。适用于在加锁期间不会出现锁竞争的情况。出现锁竞争时,升级为重量级锁。
6.重量级锁:会把轻量级锁指向栈记录的指针指向monitor。
7.每一个对象都有一个Monitor,Monitor是基于C++实现的,由ObjectMonitor实现的,参数如下:
		ObjectMonitor() {
		    _header       = NULL;
		    _count        = 0;         //用来记录该线程获取锁的次数
		    _waiters      = 0,          //存放处于wait状态的队列
		    _recursions   = 0;
		    _object       = NULL;
		    _owner        = NULL;      // 指向持有Monitor对象的线程
		    _WaitSet      = NULL;
		    _WaitSetLock  = 0 ;
		    _Responsible  = NULL ;
		    _succ         = NULL ;
		    _cxq          = NULL ;
		    FreeNext      = NULL ;
		    _EntryList    = NULL ;    //存放处于等待锁block状态的队列
		    _SpinFreq     = 0 ;
		    _SpinClock    = 0 ;
		    OwnerIsThread = 0 ;
		    _previous_owner_tid = 0;
  }

分布式锁(上面的锁只能适用在单个jvm中)

数据库方式
redis的方式
zokeeper方式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值