学习内容整理记录,如有错误感谢指正,互相交流,共同进步
单例模式思考总结
-
单例模式是指全局只生成一个对象供调用
-
这种模式减少了对象重复创建的开销
-
单例模式慎用状态,因为对象是共享的,所以状态也是共享的,容易引起线程安全问题
单例模式与线程、堵塞的关系的问题思考
-
单例模式与单线程、多线程、堵塞没有直接的关系,
-
是否堵塞与是否为单例模式没有直接关系,与其实现是否存在资源竞争有关系
-
可以理解为单例模式只是提供信息,所以不存在堵塞问题
-
网上一个很好的例子,老师在黑板上写上题目“1+1=?”,同学们都能看到题目,这里的题目是单例模式,同学们则为多线程,同学们都在自己的作业本上进行“1+1=?”的运算,作业本为线程栈,可以进行自身的运算,产生的对象放在堆里,同学之间互不影响,线程之间互不堵塞
-
但是,这时候老师设置了一个公共答案对象A“1+1=A”,允许同学们计算完后修改这个A答案,且只允许同步修改,那么同学们之间就存在竞争关系,竞争A对象资源,线程之间有堵塞。
-
所以,是否堵塞与是否单例无关,与是否需要竞争资源有关,单例模式只是提供了共享的信息,让每个线程自己去处理
-
spring中 servlet容器是多线程,也就是request请求是多线程处理的,controller 和service都是单例模式,若无设置资源竞争的情况,线程间互不影响,即可多个请求同时请求一个controller里面的方法。若方法里面存着竞争资源的使用,则存在堵塞否则互不影响
下面先介绍一下单例模式,然后用代码例子验证上述问题
- 单例模式的两种普遍写法:饿汉模式、懒汉模式
/**
* 饿汉式单例模式
* 特点:类加载时即完成对象初始化、影响加载时间、节省运行调用时间
*/
public class SingleObjectA {
//构造私有化
private SingleObjectA() {
}
//创建静态对象,加载类时即初始化
private static SingleObjectA instance=new SingleObjectA();
//获取对象实例方法
public static SingleObjectA getInstance(){
return instance;
}
}
/**
* 懒汉式单例模式
* 运行时调用才初始化实例,节省加载时间,影响运行调用时间
*/
public class SingleObjectB {
//构造私有化
private SingleObjectB() {
}
//创建静态对象,但不初始化
private static volatile SingleObjectB instance;
/**
* 获取对象实例方法,需要加入同步锁,避免并发调用时创建多个实例
* 缺点:同步锁加在方法上,也就是说每次调用都是同步的,影响并发
*/
public static synchronized SingleObjectB getInstance(){
if(instance==null){
return new SingleObjectB();
}else {
return instance;
}
}
/**
* 双重校验模式,降低同步锁的粒度,减少并发影响
* 当发现对象为null时,才把对象类锁住,这样后续实例化之后就不再触发锁机制
* 双重校验:获得锁之后再次校验是避免在判断为null后获得锁期间其它线程已完成了初始化
*
*/
public static SingleObjectB getInstanceQuickly(){
if(instance==null){
synchronized (SingleObjectB.class) {
//锁住对象后需要再次验证是否为null,是避免在获取资源期间,已经有线程初始化了SingleObjectB
if(instance==null){
return instance;
}else {
instance=new SingleObjectB();
return instance;
}
}
}else {
return instance;
}
}
}
- 单例模式与线程、堵塞之间的关系验证
- 我们创建一个controller类,并设计两个方法和一个公共变量,在spring中,bean都是单例模式,也就是说controller类是单例模式
- 执行请求方式:请求方法testA,紧接着请求两次方法testB
@RestController
@RequestMapping("/single")
public class SingleTest {
private Integer publicInt=0;
//修改公共变量+10,输出变量,睡眠10s,输出变量
@GetMapping("/testA")
public void testA() throws Exception{
publicInt=publicInt+10;
System.out.println("A开始睡眠:"+publicInt);
Thread.sleep(10*1000);
System.out.println("A结束睡眠:"+publicInt);
}
//修改公共变量+10,输出变量
@GetMapping("/testB")
public void testB() {
publicInt=publicInt+10;
System.out.println("B:"+publicInt);
}
}
请求结果:
A开始睡眠:10
B:20
B:30
A结束睡眠:30
得出结论:请求单例对象的不同方法并未相互影响,方法testA的睡眠并不影响方法testB
- 请求方式:请求方法testA,紧接着再请求方法testA
请求结果:
A开始睡眠:10
A开始睡眠:20
A结束睡眠:20
A结束睡眠:20
得出结论:请求相同方法依旧不会相互影响,可验证上述单例与线程、堵塞之间的关系。单例只是信息共享,与线程执行、资源竞争无关
- 增加验证:创建一个service,service里面有两个方法,有个是同步方法,一个是非同步方法
@Service
public class SyncObject {
//同步方法,视为竞争资源,需要竞争同步锁
public void getSyncObject(String key)throws Exception{
synchronized (SyncObject.class){
System.out.println(key+"获取同步锁竞争资源维持5s");
Thread.sleep(5*1000);
System.out.println(key+"释放同步锁竞争资源");
}
}
//非同步方法,不需要竞争
public void getObject(String key)throws Exception{
System.out.println(key+"获取非同步锁竞争变量");
}
}
请求方式:请求方法testA,紧接着请求方法testB
@Autowired
SyncObject syncObject;
@GetMapping("/testA")
public void testA() throws Exception{
publicInt=publicInt+10;
System.out.println("A方法开始");
syncObject.getObject("A");
syncObject.getSyncObject("A");
}
@GetMapping("/testB")
public void testB() throws Exception{
System.out.println("B方法开始");
syncObject.getObject("B");
syncObject.getSyncObject("B");
}
执行结果:
A方法开始
A获取非同步锁竞争变量
A获取同步锁竞争资源维持5s
B方法开始
B获取非同步锁竞争变量
A释放同步锁竞争资源
B获取同步锁竞争资源维持5s
B释放同步锁竞争资源
得出结论:方法testA和testB互不影响,当遇到竞争资源时才会阻塞
结论:
-
单例模式与多线程、堵塞并无直接关系,是否堵塞与方法中是否需要竞争同步资源有关系,否则多线程调用单例模式的情况下线程间互不影响