Synchronized和lock区别
何为可重入锁?当一个线程获得了当前实例的锁,并进入方法a,这个线程在没有释放这把锁的时候,可以再次进入方法a。
一、简介
Java 除了使用关键字 synchronized 外,还可以使用 ReentrantLock 实现排他锁(Exclusive Locks)的功能。而且 ReentrantLock 相比 synchronized 而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。ReentrantLock 常常对比着 synchronized 来分析,先对比着来看然后再一点一点分析。
1️⃣synchronized 是排他锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock 也是排他锁锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
2️⃣synchronized 可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁。ReentrantLock 也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
3️⃣synchronized 不可响应中断,一个线程获取不到锁就一直等着。ReentrantLock 可以响应中断。
synchronized 所没有的,一个最主要的就是 ReentrantLock 可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。
二、使用
1️⃣简单使用:一个最基础的使用案例,也就是实现锁的功能。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> test(), "线程A").start();
new Thread(() -> test(), "线程B").start();
}
public static void test() {
for (int i = 0; i < 2; i++) {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取了锁");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
这里定义了一个 ReentrantLock,然后在 test 方法中分别 lock 和 unlock,运行一遍就可以实现功能。这就是最简单的功能实现,代码很简单。再看看 ReentrantLock 和 synchronized 不一样的地方,那就是公平锁的实现。
2️⃣公平锁实现
对于公平锁的实现,要结合可重入性质。
public class ReentrantLockDemo {
private static final Lock lock = new ReentrantLock(true);
public static void main(String[] args) {
new Thread(() -> test(), "线程A").start();
new Thread(() -> test(), "线程B").start();
new Thread(() -> test(), "线程C").start();
new Thread(() -> test(), "线程D").start();
new Thread(() -> test(), "线程E").start();
}
public static void test() {
for (int i = 0; i < 2; i++) {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取了锁");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
new 一个 ReentrantLock 的时候参数为 true,表明实现公平锁机制。在这里多定义几个线程ABCDE,然后在 test 方法中循环执行了两次加锁和解锁的过程:
3️⃣非公平锁实现
4️⃣响应中断
响应中断就是一个线程获取不到锁,不会傻傻的一直等下去,ReentrantLock 会给予一个中断回应。在这里举一个死锁的案例:
public class ReentrantLockDemo {
private static final Lock lockA = new ReentrantLock();
private static final Lock lockB = new ReentrantLock();
public static void main(String[] args) {
Thread threadA = new Thread(new ThreadDemo(lockA, lockB));
Thread threadB = new Thread(new ThreadDemo(lockA, lockB));
threadA.start();
threadB.start();
threadA.interrupt();//第一个线程中断
}
static class ThreadDemo implements Runnable {
Lock firstLock;
Lock secondLock;
public ThreadDemo(Lock firstLock, Lock secondLock) {
this.firstLock = firstLock;
this.secondLock = secondLock;
}
@Override
public void run() {
try {
firstLock.lockInterruptibly();
TimeUnit.MINUTES.sleep(50);
secondLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
firstLock.unlock();
secondLock.unlock();
System.out.println(Thread.currentThread().getName() + "获取到了资源,正常结束!");
}
}
}
}
在这里定义了两个锁 lockA 和 lockB。然后使用两个线程 threadA 和 threadB 构造死锁场景。正常情况下,这两个线程相互等待获取资源而处于死循环状态。但是此时 threadA 中断,另外一个线程就可以获取资源,正常地执行了。测试结果:
5️⃣限时等待
通过tryLock方法来实现,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true 表示获取锁成功,false 表示获取锁失败。可以将这种方法用来解决死锁问题。首先还是测试代码,不过在这里不需要再去中断其中的线程了,直接看线程类是如何实现的:
static class ThreadDemo implements Runnable {
Lock firstLock;
Lock secondLock;
public ThreadDemo(Lock firstLock, Lock secondLock) {
this.firstLock = firstLock;
this.secondLock = secondLock;
}
@Override
public void run() {
try {
if (!lockA.tryLock()) {
TimeUnit.MILLISECONDS.sleep(10);
}
if (!lockB.tryLock()) {
TimeUnit.MILLISECONDS.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
firstLock.unlock();
secondLock.unlock();
System.out.println(Thread.currentThread().getName() + "正常结束!");
}
}
}
在这个案例中,一个线程获取 lockA 时候第一次失败,那就等10毫秒之后第二次获取,就这样一直不停的调试,一直等到获取到相应的资源为止。当然,可以设置 tryLock 的超时等待时间 tryLock(long timeout,TimeUnit unit),也就是说一个线程在指定的时间内没有获取锁,那就会返回 false,就可以再去做其他事了。
网友评论