对Java中的锁进行一个大致的了解。当然这里的分类是指从多种标准去评价,并不是说锁的种类。

锁的7大分类

  1. 偏向锁/轻量级锁/重量级锁
  2. 可重入锁/非可重入锁
  3. 共享锁/独占锁
  4. 公平锁/非公平锁
  5. 悲观锁/乐观锁
  6. 自旋锁/非自旋锁
  7. 可中断锁/不可中断锁

对于1、2、3、4、5参考文章:
对锁的理解

自旋锁/非自旋锁

自旋锁的理念是如果线程现在拿不到锁,并不直接陷入阻塞或者释放CPU资源,而是开始利用循环,不停地尝试获取锁。非自旋锁相反,拿不到锁就直接放弃。

可中断锁/不可中断锁

synchronized关键字修饰的锁代表的就是不可中断锁。线程一旦去申请锁,就没有回头路,要么拿到锁,要么就阻塞等待。ReentrantLock是典型的可中断锁。使用lockInterruptibly方法在获取锁的过程中不想获取了,就可以中断去干其他事情。


悲观锁和乐观锁不赘述。乐观锁利用CAS算法实现。一些典型案例

  • 悲观锁:synchronized和Lock接口
  • 乐观锁:原子类
  • 数据库:同时存在,例如在使用select for update语句,那就是悲观锁,我们也可以手动维护一个字段version实现乐观锁。

悲观锁适用于并发写入多、临界区代码复杂、竞争激烈场景。此时悲观锁可以避免无用的反复尝试、等待等消耗

乐观锁适用于大部分是读、少部分是修改的场景。


synchronized背后的monitor

public synchronized void method() {
    method body
}

这段代码等同于

//伪代码
public void method() {
    this.intrinsicLock.lock();
    try{
        method body
    }
    finally {
        this.intrinsicLock.unlock();
    }
}

每个Java对象都可以用于一个实现同步的锁,这个锁被称为内置锁或monitor。synchronized修饰的代码块和方法实现的细节不一样。

同步代码块

public class SynTest {
    public void synBlock() {
        synchronized (this) {
            System.out.println("lagou");
        }
    }
}

反编译结果

  public void synBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                      // String lagou
         9: invokevirtual #4               // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return

有一个monitorenter和两个monitorexit。
JVM保证一个monitorenter对应一个monitorexit,因此两个monitorexit插到了一个正常结束一个异常结束的地方。

monitorenter

执行monitorenter的线程尝试获取monitor所有权,会发生以下情况

  • 该monitor的计数为0,则该线程获取该monitor并将其设置为1.然后该线程称为此monitor的所有者
  • 如果线程已经拥有了该monitor,重新进入,累加计数
  • 其他线程拥有该monitor,这个线程被阻塞知道monitor的计数变0,才继续尝试获取

monitorexit

作用是将monitor的计数器减1.

同步方法

public synchronized void synMethod() {
 
}

反编译后结果

  public synchronized void synMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 16: 0

这里有了一个ACC_SYNCHRONIZED的标志,当某个线程执行某个方法时,会检查方法有没有这个标记位,有则需要先活的monitor锁,才能进入该方法。

synchronized和Lock

  • 两者都遵循happens-before原则。
  • lock必须显示加锁和解锁。
  • lock解锁顺序可以不必与加锁顺序对应上

Lock常用的方法

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

lock()

最基础的获取锁的办法,此方法获取锁时,如果锁被其他线程获取到,则进行等待。lock方法不能被中断,一旦陷入死锁就会永久等待

tryLock()

这是尝试去获取锁,没有获取到不会等待,而是返回false。
典型用法如下

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //处理任务
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则做其他事情
}

tryLock会解决死锁问题

    public void tryLock(Lock lock1, Lock lock2) throws InterruptedException {
        while (true) {
            if (lock1.tryLock()) {
                try {
                    if (lock2.tryLock()) {
                        try {
                            System.out.println("获取到了两把锁,完成业务逻辑");
                            return;
                        } finally {
                            lock2.unlock();
                        }
                    }
                } finally {
                    lock1.unlock();
                }
            } else {
                Thread.sleep(new Random().nextInt(1000));
            }
        }
    }

因为tryLock只是尝试去获取,不会等待.因此避免了一个线程获取了lock1,另一个线程获取lock2,此时两个线程互相等待的情况!

tryLock(long time,TimeUnit unit)

顾名思义,尝试获取锁时,没有获取到会在指定时间段内继续尝试。

lockInterruptibly()

这是一个在没有获取到锁处于等待中时能被中断的方法。本身抛出InterruptedException
用法如下:

    public void lockInterruptibly() {
        try {
            lock.lockInterruptibly();
            try {
                System.out.println("操作资源");
            } finally {
                lock.unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

unlock()

用于解锁,即计数器减1;

Last modification:April 10th, 2020 at 07:57 pm