这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战
WangScaler: 一个用心创作的作者。
声明:才疏学浅,如有错误,恳请指正。
两者对比
- synchronized是属于jvm层面的的关键字,底层通过monitorenter、monitorexit指令实现的;而lock是属于一个类。
- synchronized在代码执行异常时或正常执行完毕后,jvm会自动释放锁;而lock不行使用lock必须加上异常处理,而且必须在finally块中写上unlock()释放锁。
- synchronized不可中断,只能等待程序执行完毕或者异常退出;而lock可通过interrupt来中断,可参考示例。
- synchronized不能精确唤醒指定的线程;而lock可以通过Condition精确唤醒。可参考示例。
- synchronized无法判断锁的状态,从而无法知道是否获取锁;而lock可以判断锁的状态,可参考示例。
中断响应
lock可以通过interrupt中断,而isInterrupted可以判断线程是否被中断。
package com.wangscaler.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author WangScaler
* @date 2021/8/14 15:41
*/
class Resource {
private Lock lock = new ReentrantLock();
private int num = 1;
protected void getLock() throws InterruptedException {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "得到了锁");
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + num + "次执行");
++num;
if (num == 10) {
System.out.println(Thread.currentThread().getName() + "即将中断");
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了锁");
}
}
}
public class LockDemo {
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(() -> {
try {
resource.getLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
}
}
首先启动线程,如果num小于10则不中断线程,此时循环执行,直到num自增到10,则执行中断Thread.currentThread().interrupt();
,此时循环条件变为false,线程结束。而synchronized一旦执行不能中断,要么执行完毕,要么程序异常。
lock精确唤醒示例
lock可以通过Condition精确唤醒。
比如我们有三个线程A、B、C,我们需要保证他们的执行顺序是A-B-C那么我们可以这样写当A线程执行完通过signal();方法来唤醒B,同理一次类推循环唤醒。
package com.wangscaler.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author WangScaler
* @date 2021/8/14 15:41
*/
class Resource {
private int num = 1;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
protected void startRunA() {
lock.lock();
try {
while (num != 1) {
condition.await();
}
for (int i = 0; i < num; i++) {
int number = i + 1;
System.out.println(Thread.currentThread().getName() + number + "次执行");
}
++num;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
protected void startRunB() {
lock.lock();
try {
while (num != 2) {
condition1.await();
}
for (int i = 0; i < num; i++) {
int number = i + 1;
System.out.println(Thread.currentThread().getName() + number + "次执行");
}
++num;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
protected void startRunC() {
lock.lock();
try {
while (num != 3) {
condition2.await();
}
for (int i = 0; i < num; i++) {
int number = i + 1;
System.out.println(Thread.currentThread().getName() + number + "次执行");
}
num = 1;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class LockDemo {
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(() -> {
for (int i = 0; i < 4; i++) {
resource.startRunA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 4; i++) {
resource.startRunB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 4; i++) {
resource.startRunC();
}
}, "C").start();
}
}
当然这些线程之间就像存在依赖关系一样,只有A能唤醒B,B唤醒C,C唤醒A。就像上述的案例三个线程都是执行4次,可以保证程序的正确执行,但是当B线程改为3次,程序就无法终止,因为C线程一直处于await状态,等待B线程的唤醒,然而B线程已经结束了。不过lock能精确的控制线程的执行顺序,而synchronized则做不到这点,synchronized只能随机唤醒线程。
获取锁的状态
而lock可以通过tryLock判断锁的状态。
package com.wangscaler.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author WangScaler
* @date 2021/8/14 15:41
*/
class Resource {
private Lock lock = new ReentrantLock();
protected void testLock() {
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() + "获取锁成功");
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + "获取锁失败");
}
}
}
public class LockDemo {
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(() -> {
resource.testLock();
}, "A").start();
new Thread(() -> {
resource.testLock();
}, "B").start();
new Thread(() -> {
resource.testLock();
}, "C").start();
}
}
tryLtrock方法尝试去获取锁,如果获取成功则返回布尔值true,如果获取失败则返回false。所以可以根据tryLtrock()来判断线程是否获的锁。
总结:
在资源竞争不是很激烈的情况下,可以选择synchronized,反之选择lock。synchronized是由jvm管理的,对程序员的要求较低,而lock则相反,如果操作不当,反而会带来严重的后果。
synchronized已经加入了线程自旋和适应性自旋以及锁消除、锁粗化、偏向锁,慢慢的从重量级锁转换成轻量级锁,优势也越来越明显。总之如何选择,大家根据实际情况进行选择吧。
来都来了,点个赞再走呗!
关注WangScaler,祝你升职、加薪、不提桶!