Lock接口

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

这 5 种方法分别是 lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()、 unlock()。

lock()

在线程获取锁的时如果锁被其它的线程获取了,就会进行等待,lock加锁和释放锁都必须以代码的形式写出来,是由我们自己释放锁。要有trycatch语 句,在try中获取资源,如果出现异常在catch中捕获,然后释放锁。
伪代码使用如下:

Lock lock = ...; lock.lock();
try{
}finally{ 
    lock.unlock();
}

一定不要忘记在finally语句中释放锁,否则是非常危险的锁会得不到释放,一旦陷入死锁就 会造成很大的隐患。

tryLock()

它可以尝试获取当前的锁,如果其他线程没有被占用,就能获取成功,返回true,反之亦然。
为代码如下:

Lock lock = ...; 
if(lock.tryLock()) { 
try{
    //业务逻辑
}finally{ 
    lock.unlock();
}
}else {
    
}

有了这个强大的 tryLock()方法我们便可以解决死锁问题
如下面的例子:
死锁是多线程编程中一种比较复杂的问题,而使用锁可以一定程度上避免死锁的发生。下面是一个简单的Java Lock锁解决死锁的例子,使用ReentrantLocktryLock方法来避免死锁。
考虑两个资源ResourceAResourceB,两个线程分别需要访问这两个资源。如果线程1先获取ResourceA,然后尝试获取ResourceB,而线程2先获取ResourceB,然后尝试获取ResourceA,就可能发生死锁。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockExample {

    private static class SharedResource {
        final Lock lock = new ReentrantLock();
    }

    private static class MyThread extends Thread {
        private final SharedResource resource1;
        private final SharedResource resource2;

        public MyThread(String name, SharedResource resource1, SharedResource resource2) {
            super(name);
            this.resource1 = resource1;
            this.resource2 = resource2;
        }

        @Override
        public void run() {
            try {
                // 尝试获取第一个资源
                resource1.lock.lock();
                System.out.println(getName() + " acquired lock on " + resource1);

                // 等待一段时间模拟处理业务逻辑
                Thread.sleep(100);

                // 尝试获取第二个资源
                if (resource2.lock.tryLock()) {
                    try {
                        System.out.println(getName() + " acquired lock on " + resource2);
                        // 执行业务逻辑
                    } finally {
                        resource2.lock.unlock();
                        System.out.println(getName() + " released lock on " + resource2);
                    }
                } else {
                    System.out.println(getName() + " couldn't acquire lock on " + resource2 + ". Releasing lock on " + resource1);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                resource1.lock.unlock();
                System.out.println(getName() + " released lock on " + resource1);
            }
        }
    }

    public static void main(String[] args) {
        SharedResource resourceA = new SharedResource();
        SharedResource resourceB = new SharedResource();

        // 创建两个线程,分别尝试获取不同的资源
        MyThread thread1 = new MyThread("Thread-1", resourceA, resourceB);
        MyThread thread2 = new MyThread("Thread-2", resourceB, resourceA);

        // 启动两个线程
        thread1.start();
        thread2.start();
    }
}

运行结果:

Thread-1 acquired lock on SharedResource@hash1
Thread-1 acquired lock on SharedResource@hash2
Thread-2 couldn't acquire lock on SharedResource@hash1. Releasing lock on SharedResource@hash2
Thread-1 released lock on SharedResource@hash2
Thread-1 released lock on SharedResource@hash1
Thread-2 acquired lock on SharedResource@hash2
Thread-2 acquired lock on SharedResource@hash1
Thread-2 released lock on SharedResource@hash1
Thread-2 released lock on SharedResource@hash2

这个输出说明线程1首先获取resource1的锁,然后等待一段时间,尝试获取resource2的锁。由于线程2已经获取了resource2的锁,线程1无法立即获取resource2的锁,因此释放了resource1的锁。随后,线程2成功获取了resource1的锁,完成了整个流程。

tryLock(long time, TimeUnit unit)

该方法可以设置超时时间,如果拿不到锁等待超了设置的时间后,线程就会放弃获取这把 锁。在等待的时间也可以中断线程,避免死锁的发生。
如下面的例子:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class TryLockExample {
    private static class SharedResource {
        final Lock lock = new ReentrantLock();
    }

    private static class MyThread extends Thread {
        private final SharedResource resource;

        public MyThread(String name, SharedResource resource) {
            super(name);
            this.resource = resource;
        }

        @Override
        public void run() {
            try {
                if (resource.lock.tryLock(500, TimeUnit.MILLISECONDS)) {
                    try {
                        System.out.println(getName() + " acquired lock on " + resource);
                        // 执行业务逻辑
                    } finally {
                        resource.lock.unlock();
                        System.out.println(getName() + " released lock on " + resource);
                    }
                } else {
                    System.out.println(getName() + " couldn't acquire lock on " + resource + " within 500 milliseconds");
     

运行结果:

Thread-1 acquired lock on SharedResource@hash1
Thread-2 couldn't acquire lock on SharedResource@hash1 within 500 milliseconds
Thread-1 released lock on SharedResource@hash1

Thread-1首先获取了锁,而 Thread-2 在500毫秒内无法获取到锁,因此放弃锁的获取。
之后Thread-1释放了锁。

lockInterruptibly()

此方法的作用是获取锁,如果这个锁可以获得,那么该方法就会立刻返回。如果获取不到 被其他线程占用了,就会一直等待。
使用为代码:

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

unlock()

就是解锁。
对于ReentrantLock执行此方法,内部就会把锁,持有锁的计数器减去1,当减去1为0时 候。此锁就被释放了。

总结

  1. 引入Lock接口: Lock 接口提供了比传统的synchronized关键字更为灵活和可扩展的锁定机制。在 java.util.concurrent.locks 包中定义了多个实现了 Lock 接口的类,其中最常用的是 ReentrantLock
  2. 获取锁: 使用 lock() 方法来获取锁。这一步类似于使用synchronized关键字进行同步,但提供了更灵活的控制。
lock.lock();
try {
    // 执行需要同步的代码块
} finally {
    lock.unlock();
}
  1. 释放锁:finally 块中使用 unlock() 方法释放锁,确保锁的释放,避免死锁和其他并发问题。
  2. 可中断性: 使用 lockInterruptibly() 方法可实现可中断的锁获取。在等待锁的过程中,线程可以被中断,以避免长时间的阻塞。
try {
    lock.lockInterruptibly(); // 可中断地获取锁
    // 执行需要同步的代码块
} catch (InterruptedException e) {
    // 处理中断异常
} finally {
    lock.unlock();
}
  1. 超时获取锁: 使用 tryLock(long time, TimeUnit unit) 方法,可以尝试在指定时间内获取锁。如果在指定时间内无法获取到锁,线程可以选择放弃或进行其他处理。
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
    try {
        // 执行需要同步的代码块
    } finally {
        lock.unlock();
    }
} else {
    // 未能在指定时间内获取到锁
}

总的来说,Lock 提供了更多的控制和灵活性,适用于更复杂的并发场景。在使用时,需要注意合理释放锁,避免死锁,并根据具体需求选择合适的锁实现和锁策略。

12-03 17:46