1.JDK下的并发安全问题
如果你是在传统行业待着可能并不会关注到并发安全问题,假如你从事着OA,CRM等系统的开发,这些系统可能访问量并不大,你书写的代码就算有并发安全的漏洞,你也可能发现不到这问题。
所以,要谈来并发安全的问题,最好的方式并不是一来就谈解决方案,而是先把并发安全的问题重新
2.场景重现
在讨论并发安全问题解决方案之前,先把这问题重现了
看上面这个图,要产生并发安全问题:有几个事情是应该同时发生的:
有很多用户(并发场景)
对同一共享资源进行操作
2.1.并发重现
OrderNumGenerator负责生成订单ID,而FileId.nextId的方法是写一个文件,取出文件里面最新的数字,然后+1, 获得结果写入原来的文件,再返回数据(这一步涉及IO的读写操作会占用很多时间,很容易产生并发安全问题)
下面是测试类
模拟50个并发,同时生成订单ID,排序后看输出的结果
可以看出最终的结果里面有大量的重复,如果在真实的项目开发中,这会发生很严重的问题
3.使用Synchronized解决方案
3.1.解决原理
3.2.解决代码
package cn.enjoy.syn;
import java.text.SimpleDateFormat;
import java.util.Date;
public class OrderNumGenerator {
//全局订单id
public static int count = 0;
public static Object lock = new Object();
public String getNumber() {
synchronized(lock){
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return simpt.format(new Date()) + "-" + ++count/+"_"+Thread.currentThread().getId()/;
}
}
}
4.Lock解决方案
4.1.解决原理
4.2.解决代码
package cn.enjoy.lock;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
public class OrderNumGenerator {
//全局订单id
public static int count = 0;
private java.util.concurrent.locks.Lock lock = new ReentrantLock();
//以lock的方式解决
public String getNumber() {
try {
lock.lock();
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String s = simpt.format(new Date()) + "-" + ++count;
return s;
}finally {
lock.unlock();
}
}
}
5.谈一个面试题
Synchronized与Lock 都能解决并发安全问题,但他们有哪些区别呢?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。
他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:可以使锁更公平,可以使线程在等待锁的时候响应中断,可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间,可以在不同的范围,以不同的顺序获取和释放锁。
整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。
通过这些描述,总结出来,区别如下:
Lock提供更灵活的功能
Lock可以实现公平锁的功能,如下图
6.总结
其实并发安全解决起来并不复杂,这问题难在发现这个问题,如果你根本就没有重视这问题,可能在项目运行过程中绝多数情况下是正确的,但如果出现了并发安全问题,可能会很大的损失。
但现在讨论的是单节点下的并发安全问题(一个JVM),但在分布式场景下可能又会出现新的问题,那这里说的问题是什么呢?