锁的概念
锁是为了保证数据一致性,必须采取的手段
锁的分类为
1.乐观锁,悲观锁{
乐观锁:认为一个线程去拿数据的时候不会有其他线程对数据进行更改,所以不会上锁
实现方式:CAS机制、版本号机制
悲观锁:悲观锁认为一个线程去拿数据时一定会有其他线程对数据进行更改。所以一个线程在拿数据的时候都会顺便加锁,这样别的线程此时想拿这个数据就会阻塞。比如Java里面的synchronized关键字的实现就是悲观锁。
实现方式:加锁。
}
2.独享锁,共享锁{
synchronized是独享锁;
可重入锁ReentrantLock是独享锁;
读写锁ReentrantReadWriteLock中的读锁ReadLock是共享锁,写锁WriteLock是独享锁。
}
3.公平锁,非公平锁
4.互斥锁,读写锁
5.可重入锁
6.分段锁
7.锁升级(锁的状态){
1.无锁--->偏向锁(严格来讲他并不是锁)--->自旋锁(也叫轻量级锁,通过不断的循环等待,相当于CPU不断的空转,消耗性能所以出现了适应性自旋CAS)--->重量锁(需要进入队列,需要调度)
锁只可以升级,不可以降级
}
synchronized
在jdk早期的时候synchronized是重量级锁,很消耗性能,随着版本的优化synchronized有一个锁升级的过程偏向锁->自旋锁(轻量级锁)->重量锁
含义:在只有一个线程抢占资源的时候,是偏向锁,偏向锁不需要竞争,直接贴一个id就能访问锁,没有锁竞争的过程,但是当另外一个资源来抢占的时候,需要竞争,那么他会自动的升级锁
常用的几种synchronize方法,获得当前锁的对象
public synchronized void method()
下面这种方法等价于上面
public void method(){
synchronized(this)}
还可以给static函数
public synchronized static void method(){}
当用你static的时候 指向的是获取Class这个类的锁
虽然synchronized可以保证多线程的安全性,但是不足以控制复杂的逻辑,需要Object对象的wait()方法和notify()方法进行控制
函数wait()可以让线程等待当前对象的通知,收到notify()通知的时候,wailt()过程中的线程释放,如果有多个线程等待,那么notify()会随机选择一个
public class BlockQueue{
private List lis = new ArrayList()
public synchronized Object pop() throws UbterruptedException{
while(list.size <=0){
this.wait() // 队列为空等待
}
if(list.size()>0){
return list.remove(0);
}
return list;
}
public synchronized void put(Object obj){
list.add(0);
this.notfit(); // 唤醒被wait等待的线程
}}
Object还提供了wait(long timeout):最大等待时间不超过多小毫秒
void wait(long timeout,int nanos):最大等待不超过多少秒和多少毫秒
notifyAll:方法唤醒所有的等待对象
ReentrantLock重入锁
ReentrantLock重入锁,他比synchronized拥有强大的功能,他可以中断,可以定时,JDK5中 在高并发情况下比synchronized性能更好,但是在JDK6中,由于JVM的优化,两者差别不是很大
同时ReentrantLock 提供了公平锁和非公平锁两种锁
公平锁:可以保证在锁的等待队列中的各个线程是公平的,锁的获取总是现进先出
非公平锁:可能会存在插队的情况
通过以下函数可以指定是否公平
public ReentrantLock(boolean fair)
使用ReentrantLock必须释放锁,一般写在finally里,否则如果程序出现异常,永远无法释放锁
相比于synchronized ,JVM虚拟机总会在最后释放synchronized锁
ReentrantLock有如下方法
lock()获得锁lockInterruptibly() 获得锁,优先响应中断tryLock() 获得锁,如果成功返回true 失败false 方法不等待,立即返回tryLock(long time) 给定时间内尝试获得锁unlock() 释放锁
ReadWriteLock读写锁
如果线程A1 A2 A3 进行写操作 B1 B2 B3 进行读操作,如果使用重入锁或者内部锁,这理论上都是读和写串行的,当B1 读 的时候,B2 B3需要进行等待,由于读操作对数据的完整性不会造成破坏,这种等待不合理。
读写锁允许多个线程同时读,使得B1 B2 B3实现并行,但是考虑到数据的完整性,写写操作和读读操作他们之间依然需要相互等待,如果在一个系统中,读操作多余写操作,那么则可以用读写锁。
定义重入锁和读写锁
定义重入锁private static Lock lock = new ReentrantLock();定义读写锁private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReedWriteLock();private static Lock readLock = reentrantReadWriteLock.readLock();private static Lock wirtLock = reentrantReadWriteLock.writeLock();
CAS
如果原始值是0 读取0–A 修改值为1—B 然后判断读取的值(A操作)和修改的值(B操作)是否相等 ,如果在没有其他线程修改操作A的情况下,原先操作A和 修改完后得到B的结果的操作A的值相等 如果相等,那么覆盖掉,如果有其他操作修改了值,在进行操作B修改的时候,有操作C修改了操作A的值为8,那么操作B时候的操作A的值为0和被操作C修改存入的值为8的操作C对比 0!=8 然后就会重新读取操作C,然后将读取的操作C的值进行操作B的操作(重复循环)
著名的CAS的操作的问题
ABA问题
描述:在进行操作B的时候,操作A的值本来是自身为0,但是别的线程修改了自身的0,改为不是自身的0的值,虽然值是0,但是不是原来的0了。
解决:加版版本号(加boolean类型的和数字类型的时间戳)
以上参考以及B站的马士兵老师的课程等整理而得的笔记。希望对于自己的提升有所帮助,知不足,求上进,如果文中有错误欢迎各位留言指出,一同讨论进步。