目录
CAS-乐观锁实现方式之一
CAS操作流程
应用1:使用CAS实现了原子类
AtomicInteger实现i++
实现原理:
应用2:使用CAS来实现自旋锁
应用3:CAS引发的ABA问题
问题描述
解决办法:引入版本号
synchronized锁升级流程
四个状态
无锁
偏向锁
轻量级锁
重量级锁
创建线程—Callable接口
juc下的常用子类
1.对象锁 juc.lock
lock需要显示加锁解锁
synchronized和lock的区别
2.Semaphore—信号量
两个核心操作:
3.CountDownLatch—大号的Join方法—计数器
死锁
如何避免死锁
CAS-乐观锁实现方式之一
CAS全名:Compare and Swap : 比较交换
这种实现不会阻塞线程,而是不断尝试更新。
CAS操作流程
V的值代表读取到的最新主内存值,A代表当前工作内存中写入的值,B则是想要写回主内存的值
1.比较内存中的值V和当前工作内存的值A是否相等
2.若相等,可以认为当前主内存的值没有被修改,就把值B写回主内存
若不相等,说明当前线程的值A已经过时了,不是最新的主内存值,此时需要将主内存的最新值V覆盖到A上,保存最新的工作内存,这时值B就不能写回主内存了,需要重新循环重新修改。
应用1:使用CAS实现了原子类
java.util.concurrent.atomic包中的所有类都是线程安全的原子类
i++本身是线程不安全的。
除了上锁之外,我们可以通过atomic包下的原子类来实现
AtomicInteger实现i++
AtomicInteger count = new AtomicInteger();
这样我们可以进行 变量 的 ++ 或者 -- 操作:
实现原理:
假如两个线程同时进行i++操作,线程1先执行了:
这是V变成了1,然后线程2执行++操作
我们会发现,线程2的A保存的是0,V!=A ,所以需要重新读取值并进行++操作。
经过重新操作后,线程2将数据写回主内存
如此循环往复,就会实现原子性
应用2:使用CAS来实现自旋锁
自旋锁已经说过了,就是获取锁失败的线程不是进入阻塞态,而是一种循环态,会在CPU上空转,不断查询当前锁的状态。
也就是说只有当this.owner==null时,代表此时没有对象占用锁,就尝试加锁操作。
应用3:CAS引发的ABA问题
问题描述
ABA问题实际上就是当一个线程做了很多事,但是值最终还是没有变化时,把这个值再写会主存,这个时候另一个线程看到的值仍是自己工作内存的值,因此会直接用,但是其实不能直接用的。
假定:两个线程同时获得同一主内存值,并存放到自己的工作内存。
此时 线程1 进行一系列的运算和修改 自己线程中的值在不断变化,但最后又变回了初始值。
将值写回主存,此时主存还是那个值,线程2直接读取了。
解决办法:引入版本号
当CAS的V==A时,我们先判断版本是否正确,在进行操作。
如果不等,那也不需要判断版本了。
synchronized锁升级流程
根据竞争的激烈程度,会进行无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁的自动升级。
这里我就直接盗图了。
四个状态
无锁
没有线程开始,没人获取锁
偏向锁
当第一个线程尝试获取锁时,就把偏向锁给线程1,若是线程1再次获取锁时,没有加锁解锁过程,就验证一下锁的持有者是否是t1即可。
当第二个线程尝试获取锁JVM就会取消偏向锁的状态,升级为轻量级锁
轻量级锁
使用CAS(自旋锁)来进行轻量级锁的获取。
此状态没有获取锁的线程就一直自旋。
重量级锁
重量级锁依赖操作系统,通过mutex实现,用户态切换到内核态。
此时线程竞争非常激烈,一般这么几种情况会导致轻量级锁升级为重量级锁:
1.多个线程竞争轻量级锁(一般来说是线程数为CPU核数的一半)
2.自旋次数超过10次以上,为了避免CPU空跑,升级为重量级锁。
3.调用了Object.wait()方法,无论竞争是否激烈都升级锁,因为wait方法需要对象monitor实现,和mutex相关
创建线程—Callable接口
和Runnable最大的区别就是带返回值,并且返回值用FutureTask子类接收
同样的,我们需要Thread接收FutrueTask对象来启动线程。
FutureTask的获取call方法的返回值,调用get方法的线程会一直阻塞直到call方法执行结束,有返回值再继续执行。
juc下的常用子类
1.对象锁 juc.lock
来自java.util.concurrent中的Lock接口也可以实现对象锁。
在JDK 1.5之后,Java语言自己实现的互斥锁实现,不需要借助操作系统的monitor机制。
lock需要显示加锁解锁
加锁:
1.死等法:
2.等会儿就算:
解锁:
synchronized和lock的区别
一图流了
2.Semaphore—信号量
信号量Semaphore是一个计数器,表示当前可用资源的个数。
两个核心操作:
1.申请资源操作:
2.释放资源操作:
我们每次也可以尝试获取和释放多个资源:
3.CountDownLatch—大号的Join方法—计数器
CountDownLatch countDownLatch = new CountDownLatch(10);
调用await方法需要等待其他线程将计数器减为0,才能继续向下执行。很像join方法。
countDownLatch.await();
调用countDown方法使计数器 -1
countDownLatch.countDown();
以上几种工具类就是juc包的常用工具类,尤其是:
信号量—Semaphore 计数器—CountDownLatch
死锁
举个例子:A线程在运行途中开始等待B线程唤醒自己
同时,B线程也在等待A线程唤醒自己
他俩谁都无法继续进行,也无法相互唤醒,全都卡在那了。
如何避免死锁
其实很简单,只要避免多个线程获取资源不要成一个循环就可以了。