直接进入主题。上IDEA操作
1、创建一个Mylock类,添加一个order方法、每次让他money+1,初始余额为0
2、在main方法里面,利用for循环创建线程模拟调用order方法,进行收钱操作
3、运行程序就能看到钱往上涨啦, 循环100次,每次100笔订单,按照预想情况我们的moeny应该为10000,实际呢?我们运行一下:
运行程序之后发现收到的moeny怎么是9997,不应该是10000吗?那我不是亏了3,是不是系统程序有问题!!!,那我们在测试一遍:
运行之后发现更严重了!完犊子了,9977了,这怎么搞?为什么会出现这样的现象呢?
4、为什么会导致这样的问题呢?
i++操作非原子性的。
因为每个线程都有自己的工作内存,每个线程需要对共享变量操作时必须先把共享变量从主内存 load 到自己的工作内存,等完成对共享变量的操作时再 save 到主内存。
问题就出在这了,如果一个线程运算完后还没刷到主内存,此时这个共享变量的值被另外一个线程从主内存读取到了,这个时候读取的数据就是脏数据了,它会覆盖其他线程计算完的值。
5、怎么解决这个问题?
(1)加锁保证顺序 (保证每次只有一个线程在进行读取写入操作)
(2)用原子性操作类 如:juc(java.util.concurrent)包下的AtomicInteger
6、lock锁(在order方法里面进行加锁)
改造一下main方法,一个个启动太累,我们一次性模拟10个调用
运行一下看看效果:
可以看到这个时候值就是预期值了,这下可以安心了,钱没有少,对上数目了。
7、那么我们看看lock是个啥
Lock.lock(加锁)
Juc包下找到了,他是定义了NonfairSync
我们看一下NonfairSync方法
NonfairSync继承了sync,sync继承了AbstractQueuedSynchronizer,这个就是面试经常遇到的AQS。内容是博大精深
Lock.lock方法,首先进行了cas获取锁。
CAS操作是调用了本地方法native
如果CAS获取了锁,就调用setExclusiveOwnerThread、设置当前线程持有该锁
否则调用acquire
主要做了以下两件事:
(1) 通过tryAcquire尝试获取锁
(2) 若是没有获取到锁,将线程加入等待队列acquireQueued
tryAcquire尝试获取锁:
(1)该方法会首先判断当前状态,如果c==0说明没有线程正在竞争该锁,如果不c !=0 说明有线程正拥有了该锁。
(2)如果发现c==0,则通过CAS设置状态的值为1,每次线程重入该锁都会+1,每次unlock都会-1,但为0时释放锁。
(3)current == getExclusiveOwnerThread()判断锁的拥有者的线程,如果是当前线程则将状态值直接+1
addWaiter将当前线程添加至队尾
(1)如果当前队尾已经存在(tail!=null),则使用CAS把当前线程更新为Tail
(2)如果当前Tail为null或线程调用CAS设置队尾失败,则通过enq方法继续设置Tail
enq循环调用cas将当前线程追加到队尾
acquireQueued 通过tryAccquire重试是否能获得锁、失败则阻塞,成功则返回
(1) 尝试获取锁、若p == head && tryAcquire(arg),则跳出循环,获取锁成功
(2) 获取锁不成功,parkAndCheckInterrupt会把当前线程进行park休眠
(3) park前当然还要检查线程的状态,shouldParkAfterFailedAcquire具体的检查在这个方法当中,当节点状态为SIGNAL,则进行park休眠
Lock.unlock(解锁)
Release如果可以释放锁,则唤醒等待队列中的线程
TryRelease,里面看起来是不是很眼熟,和TryAcquire差不都,一个是加锁,状态+1,这里是解锁 状态-1,当为0的时候,将当前锁的拥有者设置为null
具体唤醒代码就在unparkSuccessor了,找出第一个可以unpark的线程,lockSupport.unpark就是唤醒线程
总结:
1、调用lock方法,先进行cas获取锁,如果成功获取锁、将状态设置为1、拥有者为当前线程
2、如果没有成功获取锁,加入等待队列进行等待
3、调用unlock方法解锁,唤醒队列里的等待线程、继续进行锁竞争