在应用开发中总是会遇到需要对方法进行加锁的场景,java中为我们提供了两种加锁方式,一是synchronized,二是lock方式。那么在实际开发中我们应该选取哪种加锁方式呢
通过阅读本篇文章,你将了解到:
| 锁 | synchronize | ReentrantLock |
|---|---|---|
| 加锁释放锁方式 | 使用者无需关心,自动加锁、释放锁 | 显式加锁、释放锁,必须调用lock方法获取锁,调用unlock方法释放锁 |
| 中断 | 不可响应中断 | 可响应中断 |
| 超时获取锁 | 不允许 | 允许 |
| 是否可以实现公平锁 | 否,默认就为非公平锁 | 是,通过构造函数指定是否为公平锁,默认为非公平锁,传入true为公平锁 |
| 实现方式 | JVM级别 | API级别 |
ReentrantLock相比synchronized更灵活一些
ReentrantLock是Lock接口的其中一个实现类,Lock接口中定义的方法有:
lock和tryLock方法除了返回值不一样以外,lock获取到的锁是不可响应中断的,而tryLock获取到的锁是可响应中断的。除此以外tryLock(long time, TimeUnit unit)获取到的锁也是可响应中断,即获取锁的方法中只有lock方法获取到的锁是不可以响应中断的
public class ReentrantLockLockInterruptiblyTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Task());
thread.start();
Thread.sleep(3 * 1000); //执行三秒后中断线程
thread.interrupt();
}
public static class Task implements Runnable {
Lock lock = new ReentrantLock();
public Task () {
new Thread(() -> {
try {
lockMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Override
public void run() {
try {
lockMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End...");
}
private void lockMethod() throws InterruptedException {
lock.lockInterruptibly();
try {
//模拟长时间不释放锁
while (true) {}
} finally {
lock.unlock();
}
}
}
}
复制代码
执行结果:
java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at com.h2t.study.concurrent.lock.ReentrantLockLockInterruptiblyTest$Task.lockMethod(ReentrantLockLockInterruptiblyTest.java:45) at com.h2t.study.concurrent.lock.ReentrantLockLockInterruptiblyTest$Task.run(ReentrantLockLockInterruptiblyTest.java:37) at java.lang.Thread.run(Thread.java:748) End... 复制代码
中断成功
public class SynchronizedBlock {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Task());
thread.start();
Thread.sleep(3 * 1000); //执行三秒后中断线程
thread.interrupt();
System.out.println(thread.isInterrupted());
}
public static class Task implements Runnable {
public Task() {
new Thread() {
public void run() {
f();
}
}.start();
}
public synchronized void f() {
while (true) {
}
}
@Override
public void run() {
f();
System.out.println("End");
}
}
}
复制代码
控制台永远不会抛出异常、打印出End
对于synchronized来说,如果一个线程在等待锁,调用中断线程的方法,不会生效即不响应中断。而lock可以响应中断
public class ReentrantLockTryLockTest {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 2; i++) {
es.execute(new Task(i));
}
}
private static class Task implements Runnable {
private static Lock lock = new ReentrantLock();
private int i;
public Task(int i) {
this.i = i;
}
@Override
public void run() {
try {
lockMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//每次只允许一个线程调用
private void lockMethod() throws InterruptedException {
long start = System.currentTimeMillis();
//2s内获得锁
if (lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println(String.format("i = %d 获取到锁,耗时:%d", i, System.currentTimeMillis() - start));
try {
Thread.sleep(1000 * 60 * 1); //睡眠1分钟
} finally {
lock.unlock();
}
} else {
System.out.println(String.format("i = %d 获取到锁失败,耗时:%d", i, System.currentTimeMillis() - start));
}
}
}
}
复制代码
run方法中调用了加锁的方法,加锁方法中尝试在2s内获得锁 执行结果:
i = 0 获取到锁,耗时:0 i = 1 获取到锁失败,耗时:2001 复制代码
public class ReentrantLockFairTest {
//通过传入true创建一个公平锁
private static Lock fairLock = new ReentrantLock(true);
//非公平锁,默认为非公平锁
private static Lock unfairLock = new ReentrantLock();
public static void main(String[] args) {
ExecutorService unfairEs = Executors.newCachedThreadPool();
ExecutorService fairEs = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
unfairEs.execute(new UnfairTask(i));
fairEs.execute(new FairTask(i));
}
}
/**
* 非公平锁任务
* */
private static class UnfairTask implements Runnable {
private int i;
public UnfairTask(int i) {
this.i = i;
}
@Override
public void run() {
unfairLock.lock();
try {
System.out.println(String.format("unfairTask i = %d is running", i));
} finally {
unfairLock.unlock();
}
}
}
/**
* 公平锁任务
* */
private static class FairTask implements Runnable {
private int i;
public FairTask(int i) {
this.i = i;
}
@Override
public void run() {
fairLock.lock();
try {
System.out.println(String.format("fairTask i = %d is running", i));
} finally {
fairLock.unlock();
}
}
}
}
复制代码
执行结果:
unfairTask i = 0 is running fairTask i = 0 is running unfairTask i = 1 is running fairTask i = 1 is running unfairTask i = 2 is running fairTask i = 3 is running unfairTask i = 3 is running fairTask i = 2 is running fairTask i = 4 is running unfairTask i = 4 is running 复制代码
公平锁先到先得,因此执行顺序是有序的。非公平锁如果后来提交的线程刚好获取释放掉的锁将获得锁先执行,因此结果执行顺序是无序的
public class SynchronizedUnfairTest {
public static void main(String[] args) {
ExecutorService unfairEs = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
unfairEs.execute(new UnfairTask(i));
}
}
/**
* 非公平锁任务
* */
private static class UnfairTask implements Runnable {
private int i;
public UnfairTask(int i) {
this.i = i;
}
@Override
public synchronized void run() {
System.out.println(String.format("unfairTask i = %d is running", i));
}
}
}
复制代码
执行结果:
unfairTask i = 1 is running unfairTask i = 0 is running unfairTask i = 3 is running unfairTask i = 2 is running unfairTask i = 4 is running 复制代码
synchronized默认为非公平锁,并且只能是非公平锁,因此执行结果顺序是无序的
public class ReentrantLockTask implements Runnable {
private int i;
public ReentrantLockTask(int i) {
this.i = i;
}
@Override
public void run() {
lockMethod();
}
ReentrantLock lock = new ReentrantLock();
private void lockMethod() {
int sum = 0;
lock.lock();
try {
for (int j = 0; j < 10; j++) {
sum += j;
}
} finally {
lock.unlock();
}
}
}
复制代码
public class SynchronizedLockTask implements Runnable {
private int i;
public SynchronizedLockTask(int i) {
this.i = i;
}
@Override
public void run() {
lockMethod();
}
private synchronized void lockMethod() {
int sum = 0;
for (int j = 0; j < 10; j++) {
sum += j;
}
}
}
复制代码
public class PerformTest {
public static void main(String[] args) {
for (int i = 100; i < 1000000000; i = i * 10) {
reentrantLockTest(i);
synchronizedLockTest(i);
}
}
/**
* 循环执行的次数
* */
private static void reentrantLockTest(int time) {
ExecutorService es = Executors.newCachedThreadPool();
long start = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
es.execute(new ReentrantLockTask(i));
}
System.out.println(String.format("ReentrantLockTest time = %d Spend %d", time, System.currentTimeMillis() - start));
}
private static void synchronizedLockTest(int time) {
ExecutorService es = Executors.newCachedThreadPool();
long start = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
es.execute(new SynchronizedLockTask(i));
}
System.out.println(String.format("SynchronizedLockTest time = %d Spend %d", time, System.currentTimeMillis() - start));
}
}
复制代码
循环执行任务,统计循环任务的耗时
ReentrantLockTest time = 100 Spend 6 SynchronizedLockTest time = 100 Spend 2 ReentrantLockTest time = 1000 Spend 7 SynchronizedLockTest time = 1000 Spend 14 ReentrantLockTest time = 10000 Spend 42 SynchronizedLockTest time = 10000 Spend 29 ReentrantLockTest time = 100000 Spend 186 SynchronizedLockTest time = 100000 Spend 156 ReentrantLockTest time = 1000000 Spend 1428 SynchronizedLockTest time = 1000000 Spend 1006 ReentrantLockTest time = 10000000 Spend 9716 SynchronizedLockTest time = 10000000 Spend 9791 ReentrantLockTest time = 100000000 Spend 97928 SynchronizedLockTest time = 100000000 Spend 99804 复制代码
synchronized和ReentrantLock性能相差不大,分不出谁好谁不好
synchronized和ReentrantLock性能差不多, 当且仅当synchronized无法满足的情景下使用ReentrantLock ,因为ReentrantLock需要显式释放锁,同时synchronized是JVM级别的,JVM能对其进行优化,而Reentrant是API级别的不会有任何优化。synchronized无法满足的情景:
最后附: 示例代码
欢迎 fork 与 star