设计模式:单例模式-单例模式与多线程、堵塞的关系

学习内容整理记录,如有错误感谢指正,互相交流,共同进步

单例模式思考总结

  1. 单例模式是指全局只生成一个对象供调用

  2. 这种模式减少了对象重复创建的开销

  3. 单例模式慎用状态,因为对象是共享的,所以状态也是共享的,容易引起线程安全问题

单例模式与线程、堵塞的关系的问题思考

  1. 单例模式与单线程、多线程、堵塞没有直接的关系,

  2. 是否堵塞与是否为单例模式没有直接关系,与其实现是否存在资源竞争有关系

  3. 可以理解为单例模式只是提供信息,所以不存在堵塞问题

  4. 网上一个很好的例子,老师在黑板上写上题目“1+1=?”,同学们都能看到题目,这里的题目是单例模式,同学们则为多线程,同学们都在自己的作业本上进行“1+1=?”的运算,作业本为线程栈,可以进行自身的运算,产生的对象放在堆里,同学之间互不影响,线程之间互不堵塞

  5. 但是,这时候老师设置了一个公共答案对象A“1+1=A”,允许同学们计算完后修改这个A答案,且只允许同步修改,那么同学们之间就存在竞争关系,竞争A对象资源,线程之间有堵塞。

  6. 所以,是否堵塞与是否单例无关,与是否需要竞争资源有关,单例模式只是提供了共享的信息,让每个线程自己去处理

  7. spring中 servlet容器是多线程,也就是request请求是多线程处理的,controller 和service都是单例模式,若无设置资源竞争的情况,线程间互不影响,即可多个请求同时请求一个controller里面的方法。若方法里面存着竞争资源的使用,则存在堵塞否则互不影响

下面先介绍一下单例模式,然后用代码例子验证上述问题

  1. 单例模式的两种普遍写法:饿汉模式、懒汉模式
/**
* 饿汉式单例模式
* 特点:类加载时即完成对象初始化、影响加载时间、节省运行调用时间
*/
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;
        }
    }
}
  1. 单例模式与线程、堵塞之间的关系验证
  2. 我们创建一个controller类,并设计两个方法和一个公共变量,在spring中,bean都是单例模式,也就是说controller类是单例模式
  3. 执行请求方式:请求方法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

  1. 请求方式:请求方法testA,紧接着再请求方法testA

请求结果:

A开始睡眠:10
A开始睡眠:20
A结束睡眠:20
A结束睡眠:20

得出结论:请求相同方法依旧不会相互影响,可验证上述单例与线程、堵塞之间的关系。单例只是信息共享,与线程执行、资源竞争无关

  1. 增加验证:创建一个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互不影响,当遇到竞争资源时才会阻塞

结论:

  1. 单例模式与多线程、堵塞并无直接关系,是否堵塞与方法中是否需要竞争同步资源有关系,否则多线程调用单例模式的情况下线程间互不影响

你可能感兴趣的:(java,#设计模式,单例模式,java,设计模式)