乐观锁和悲观锁
悲观锁: 假设在最坏的情况下,去拿数据的时候,认为别人会修改数据,所以在访问数据的时候,就要把锁加上,那么其他线程来获取这个数据的时候,先阻塞直到获得锁对象。传统关系型数据库就是利用这种锁机制来实现,读写操作,java里面可以利用synchronized 和ReenrantLock 来实现
乐观锁: 在最后的情况下,每次访问数据的时候不会有其他线程来修改这个数据,不用加锁。但是在更新这个数据的时候会去判断有没有线程更新过,利用CAS(compare and swap)算法来实现。
java.util.concurrent包下面的原子类利用了CAS实现。
同时乐观锁多应用与读的场景,这样可以很好的提高吞吐量
乐观锁的两种实现方式 :
版本号 一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,
若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
CAS算法 比较与交换 无锁编程,在不使用锁的情况下实现多线程之间的同步问题。
CAS算法设计的三个值:需要读写的内存值V 进行比较的值 A 拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
CAS带来的问题:
ABA问题
1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。
2、什么是”ABA”问题?
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
AtomicStampedReference 类来解决这个问题
只能保证单个共享变量的原子操作
自旋时间太长影响CPu执行效率
关于synchronized关键字原理:
同步块。同步方法 同步静态方法(锁当前的Class对象)JVM里面用两个指令 monitorenter moniterexit指令来分别表示进入 和退出锁
synchronized 锁开销比较大 为了进一步优化 引入了轻量锁 偏向锁
轻量锁
当线程进入到同步块时,如果同步对象为无锁的时候,当前线程会在栈帧中创建一个锁记录区域,把锁对象的Mark word的数据拷贝到这个区域,在利用CAS算法来更新记录。如果更新成功就表示获得锁对象了。否则检查当前记录区域的数据示指向当前的锁对象吗?如果是则直接进入,说明当前线程拥有该锁对象的锁,否则不是,存在多个线程同时竞争一把锁,则轻量锁升级为重量锁。
偏向锁 的特征,不存在多线程竞争,应该让一个线程多次获得锁
当线程访问同步代码块的时候,则利用CAS那线程的ID更新到锁对象的MArk word,如果更新成功,则表示获取锁成功,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
那么其他线程想要获取偏向锁,怎么办?
当其他线程想要获取偏向锁的时候,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),
接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态。乐观锁和悲观锁
悲观锁: 假设在最坏的情况下,去拿数据的时候,认为别人会修改数据,所以在访问数据的时候,就要把锁加上,那么其他线程来获取这个数据的时候,先阻塞直到获得锁对象。传统关系型数据库就是利用这种锁机制来实现,读写操作,java里面可以利用synchronized 和ReenrantLock 来实现
乐观锁: 在最后的情况下,每次访问数据的时候不会有其他线程来修改这个数据,不用加锁。但是在更新这个数据的时候会去判断有没有线程更新过,利用CAS(compare and swap)算法来实现。
java.util.concurrent包下面的原子类利用了CAS实现。
同时乐观锁多应用与读的场景,这样可以很好的提高吞吐量
乐观锁的两种实现方式 :
版本号 一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,
若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
CAS算法 比较与交换 无锁编程,在不使用锁的情况下实现多线程之间的同步问题。
CAS算法设计的三个值:需要读写的内存值V 进行比较的值 A 拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
CAS带来的问题:
ABA问题
1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。
2、什么是”ABA”问题?
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
AtomicStampedReference 类来解决这个问题
只能保证单个共享变量的原子操作
自旋时间太长影响CPu执行效率
关于synchronized关键字原理:
同步块。同步方法 同步静态方法(锁当前的Class对象)JVM里面用两个指令 monitorenter moniterexit指令来分别表示进入 和退出锁
synchronized 锁开销比较大 为了进一步优化 引入了轻量锁 偏向锁
轻量锁
当线程进入到同步块时,如果同步对象为无锁的时候,当前线程会在栈帧中创建一个锁记录区域,把锁对象的Mark word的数据拷贝到这个区域,在利用CAS算法来更新记录。如果更新成功就表示获得锁对象了。否则检查当前记录区域的数据示指向当前的锁对象吗?如果是则直接进入,说明当前线程拥有该锁对象的锁,否则不是,存在多个线程同时竞争一把锁,则轻量锁升级为重量锁。
偏向锁 的特征,不存在多线程竞争,应该让一个线程多次获得锁
当线程访问同步代码块的时候,则利用CAS那线程的ID更新到锁对象的MArk word,如果更新成功,则表示获取锁成功,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
那么其他线程想要获取偏向锁,怎么办?
当其他线程想要获取偏向锁的时候,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),
接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态。