点击上方疾风先生可以订阅哦
锁的类型
乐观锁与悲观锁
// 使用java以及数据库的操作方式演示乐观锁与悲观锁
// optimistic.java
// 乐观锁
AtomicInteger count = new AtomicInteger();
run(){
// cas instance
int expected = 10;
int updated = 12;
count.compareAndSet(expected, updated);
// db instance
Object row = selectById();
// update bussiness data except row version
row.setXX();
// update data
updateRowByIdWithVersion(row);
// db.execute("update tb_entity set x1=?,x2=?,version=#{version}+1 where id=#{id} and version=#{version}", row);
}
// pessimistic.java
// 悲观锁
run(){
// mutex lock
synchronized(this){
// code ..
}
// db lock
// start transaction
db.execute("select * from tb_entity where id=#{id} for update"); // lock
db.execute("update tb_entity set x1=?,... where id=#{id}"); // update
// commit trasaction;
}
可重入锁与递归锁
// source.java
final static ReentrantLock reentrantLock = new ReentrantLock();
run(){
reentrantLock.lock();
// code ...
{
reentrantLock.lock();
// code ...
reentrantLock.unlock();
}
// code ...
reentrantLock.unlock();
}
// order.java
synchronized void createOutBoundOrder(){
//...
}
run(){
//Order sharedOrder = new Order();
synchronized(sharedOrder){
// create order item
// get logistics price and caculate the best optimisic algorithm
createOutBoundOrder(orders);
// dispatch order towards sys
}
}
公平锁与非公平锁
// 公平锁,会消耗性能
ReentrantLock pairLock = new ReentrantLock(true);
// 非公平锁, 默认为不公平锁,获取锁的方式是随机,不按照线程争抢锁的顺序
ReentrantLock unpairLock = new ReentrantLock(false);
共享锁(读)与独占锁(写)
// read_write_lock.java
private static void readWriteLock() {
final Lock read = reentrantReadWriteLock.readLock();
final Lock write = reentrantReadWriteLock.writeLock();
for (int index = 0; index < 10; index++){
// 读取操作交替执行,所有读线程都能够获取读锁
new Thread(){
@Override
public void run() {
try{
read.lock();
read();
}finally {
read.unlock();
}
}
}.start();
// 写操作按照获取锁的方式顺序执行,只能在单线程中实现写操作
new Thread(){
@Override
public void run() {
try{
write.lock();
write();
}finally {
write.unlock();
}
}
}.start();
}
}
自旋锁 = CAS无锁 + 循环
-XX:PreBlockSpinsh
来设置对应的自旋失败次数// 使用CAS无锁 + 循环的方式 -- 自旋锁
private static Unsafe unsafe;
private static long valueOffsetValue = 0;
private volatile int count = 0;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
// CAS 硬件原语 ---java语言 无法直接改内存。曲线通过对象及属性的定位方式
valueOffsetValue = unsafe.objectFieldOffset(LockCASDemo.class.getDeclaredField("count"));
} catch (Exception e) {
e.printStackTrace();
}
}
private void countAdder(){
// 自增加
int current;
int value;
do{
current = unsafe.getIntVolatile(this, valueOffsetValue); // 读取当前值
value = current + 1; // 计算
}while (unsafe.compareAndSwapInt(this, valueOffsetValue, current , value));
}
jdk锁的细节
// - java技术伪代码
// business.java
private Lock lock = new ReentrantLock();
void doBusiness(){
// code ...
// 锁响应中断,如果主线程对当前线程进行中断操作,那么当前线程将会直接退出线程
lock.lockInterruptibly();
// 如果主线程对当前线程进行中断操作,那么当前线程会继续获取锁之后再进行中断操作
// lock.lock();
try{
// execute core should spent 10s
TimeUnit.SECONDS.sleep(10L);
}catch(Exception e){
LOG.error(e);
}finally{
lock.unlock();
}
}
// main.java
// 伪代码,为了简化代码的编写
Thread t1,t2 = new Thread(){
public void run(){
try{
doBusiness();
doOthers();
}catch(InterruptedException e){
LOG.error(e);
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(1L);
t2.start();
TimeUnit.SECONDS.sleep(2L);
// 线程2中断
t2.interrupt();
// 使用线程通信方式
final Condition full = reentrantLock.newCondition();
final Condition empty = reentrantLock.newCondition();
final int size = 10;
final ArrayDeque<String> container = new ArrayDeque<>();
Thread producer = new Thread(){
@Override
public void run() {
reentrantLock.lock();
try{
// 不断生产数据
while (true){
// 数据已经满了
while (size == container.size()){
// 不再进行生产数据
full.await();
}
// 生产数据
container.push(new String("xxx"));
System.out.println(Thread.currentThread().getName() + " produce ...");
empty.signalAll();// 通知消费进行消费
}
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
};
Thread consumer = new Thread(){
@Override
public void run() {
reentrantLock.lock();
try{
while (true){
// 判断数据是否为空
while(0 == container.size()){
empty.await();
}
String str = container.pop();
System.out.println(Thread.currentThread().getName() + " consumer data for :" + str);
full.signalAll(); // 通知生产者生产数据
}
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
};
producer.start();
TimeUnit.SECONDS.sleep(2L);
consumer.start();
JVM锁的优化方式
synchronized加锁方式的优化
减少锁持有时间
run(){
// query from user
// query from order
try{
// 保证一致性, 思考: 这里用事务也可以实现同样的效果,事务与锁有什么关系?
lock.lock();
// create order items
// create orders
// create outbound orders
}finally{
lock.unlock();
}
}
参考mysql文档: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-transaction-model.html
参考美团技术文档: https://tech.meituan.com/2014/08/20/innodb-lock.html
锁粗化
// 锁粗化伪代码示例
increase(){
synchronized(this){
this.productNum -- ;
}
// send message to mq notice product num have been decreased
synchronized(this){
this.orderItem ++ ;
}
// shopping cart have add new one product one
}
take(){
for(int index=0; index < LIMIT_SIZE; index++){
synchronized(queue){
queue.pop();
}
}
}
// 从上述看到,其实锁粗化出现的情况很多时候是我们是在指定的业务顺序逻辑进行编写代码造成的,一般情况下对代码的优化我们也需要考虑到两个同步代码中间的其他代码是否不影响执行程序代码的执行结果来进行优化
increase(){
// 减少锁的获取
synchronized(this){
this.productNum -- ;
this.orderItem ++ ;
}
// send message to mq notice product num have been decreased
// shopping cart have add new one product one
}
take(){
synchronized(queue){
for(...){
// ...
}
}
}
int i = 0;
test1(){
synchronized(this){
i++;
}
synchronized(this){
i++;
}
}
// main函数
for (int i = 0; i < 1000000; i++) {
new LockOptmistic().test1()
}
// 分别截图JIT编译 i < 10000 & i < 100000 & i < 1000000 对应的效果
锁消除
// 锁消除的伪代码
test2(){
// 线程封闭,属于线程私有的变量,此时非共享资源,加锁没有意义,JIT编译器会自动将锁进行消除
Object object = new Object();
int count = 0;
synchronized(object){
count++;
}
}
// 出现上述的原因很多时候是由于工程师本身在编译不规范造成的,这种并非业务性逻辑错误,可以避免的
缩小锁的粒度(分片锁)
锁分离
JVM锁相关原理梳理
你好,我是疾风先生,先后从事外企和互联网大厂的java和python工作, 记录并分享个人技术栈,欢迎关注我的公众号,致力于做一个有深度,有广度,有故事的工程师,欢迎成长的路上有你陪伴,关注后回复greek可添加私人微信,欢迎技术互动和交流,谢谢!