在应用开发中总是会遇到需要对方法进行加锁的场景,java中为我们提供了两种加锁方式,一是synchronized,二是lock方式。那么在实际开发中我们应该选取哪种加锁方式呢
写在前面
通过阅读本篇文章,你将了解到:
- synchronized和ReentrantLock的不同点、相同点
- ReentrantLock、synchronized响应中断测试
- ReentrantLock#tryLock定时获取锁测试
- ReentrantLock公平锁、非公平锁测试
- ReentrantLock和synchronized性能测试
- ReentrantLock应用场景介绍
相同点
- 互斥 => 同时只有一个线程获取锁
- 内存可见性 => 对共享变量的修改对另一个线程立即可见
- 可重入 => 同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞
不同点
锁 | synchronize | ReentrantLock |
---|---|---|
加锁释放锁方式 | 使用者无需关心,自动加锁、释放锁 | 显式加锁、释放锁,必须调用lock方法获取锁,调用unlock方法释放锁 |
中断 | 不可响应中断 | 可响应中断 |
超时获取锁 | 不允许 | 允许 |
是否可以实现公平锁 | 否,默认就为非公平锁 | 是,通过构造函数指定是否为公平锁,默认为非公平锁,传入true为公平锁 |
实现方式 | JVM级别 | API级别 |
ReentrantLock相比synchronized更灵活一些
ReentrantLock方法测试
ReentrantLock是Lock接口的其中一个实现类,Lock接口中定义的方法有:
- void lock() => 立即获取锁
- boolean tryLock() => 立即获取锁
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException => 在指定时间内获取锁,获得锁返回true,获取不到锁返回false
- void lockInterruptibly() throws InterruptedException => 获取可中断锁
- void unlock() => 释放锁
- Condition newCondition() => 返回绑定到此 Lock 实例的Condition 实例
lock和tryLock方法除了返回值不一样以外,lock获取到的锁是不可响应中断的,而tryLock获取到的锁是可响应中断的。除此以外tryLock(long time, TimeUnit unit)获取到的锁也是可响应中断,即获取锁的方法中只有lock方法获取到的锁是不可以响应中断的
lockInterruptibly响应中断:ReentrantLockLockInterruptiblyTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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(); } } } } |
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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... |
中断成功
synchronize响应中断:SynchronizedBlock.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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可以响应中断
tryLock定时锁:ReentrantLockTryLockTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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内获得锁
执行结果:
1 2 | i = 0 获取到锁,耗时:0 i = 1 获取到锁失败,耗时:2001 |
ReentrantLock公平锁与非公平锁:ReentrantLockFairTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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(); } } } } |
执行结果:
1 2 3 4 5 6 7 8 9 10 | 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 |
公平锁先到先得,因此执行顺序是有序的。非公平锁如果后来提交的线程刚好获取释放掉的锁将获得锁先执行,因此结果执行顺序是无序的
synchronized非公平锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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)); } } } |
执行结果:
1 2 3 4 5 | 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默认为非公平锁,并且只能是非公平锁,因此执行结果顺序是无序的
性能测试
ReentrantLock加锁任务:ReentrantLockTask.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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(); } } } |
synchronized加锁任务:SynchronizedLockTask.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } } } |
测试类:PerformTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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)); } } |
循环执行任务,统计循环任务的耗时
测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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性能差不多,当且仅当synchronized无法满足的情景下使用ReentrantLock,因为ReentrantLock需要显式释放锁,同时synchronized是JVM级别的,JVM能对其进行优化,而Reentrant是API级别的不会有任何优化。synchronized无法满足的情景:
- 定时获取锁
- 响应中断
- 需要以公平的方式获取锁
最后附:示例代码
欢迎fork与star
附往期文章:欢迎你的阅读、点赞、评论
并发相关
1.为什么阿里巴巴要禁用Executors创建线程池?
2.自己的事情自己做,线程异常处理
设计模式相关:
1. 单例模式,你真的写对了吗?
2. (策略模式+工厂模式+map)套餐 Kill 项目中的switch case
JAVA8相关:
1. 使用Stream API优化代码
2. 亲,建议你使用LocalDateTime而不是Date哦
数据库相关:
1. mysql数据库时间类型datetime、bigint、timestamp的查询效率比较
2. 很高兴!终于踩到了慢查询的坑
高效相关:
1. 撸一个Java脚手架,一统团队项目结构风格
日志相关:
1. 日志框架,选择Logback Or Log4j2?
2. Logback配置文件这么写,TPS提高10倍
工程相关:
1. 闲来无事,动手写一个LRU本地缓存
2. Redis实现点赞功能模块
3. JMX可视化监控线程池
4. 权限管理 【SpringSecurity篇】
5. Spring自定义注解从入门到精通
6. java模拟登陆优酷
7. QPS这么高,那就来写个多级缓存吧
8. java使用phantomjs进行截图
其他:
1. 使用try-with-resources优雅关闭资源
2. 老板,用float存储金额为什么要扣我工资