zoukankan      html  css  js  c++  java
  • 设计模式之单例模式

    单例模式:确保一个类只有一个实例,并提供一个全局的访问点。

    在单例模式下,当需要返回单个实例时,通过单件类获取是唯一的途径。

    案例代码下载

    情景:小明家只有一辆车,车在某一个时刻,只有一个状态,要么前进,要么后退,也就是倒车。

    案例代码:

    在正规的单例模式中,单例类需要提供似有的构造方法,通过共有的全局访问点。在本测试代码中为了比较差异,对单例模式稍作改动。

    单例模式只允许创建一个对象,为了检查是不是一个对象,我把对象的hashCode打印出来了。

    1、Test.java

    public class Test {
    	public static void main(String args[]){
    		//小明家只有一辆汽车
    		//一个类可以有好多个实例
    		Car c_a = new Car();
    		Car c_b = new Car();
    		
    		c_a.driverBackward();
    		c_b.driverForward();
    		
    		System.out.println(c_a);//Car [mName=小明家的汽车, mState=倒车]hashCode373882728
    		System.out.println(c_b);//Car [mName=小明家的汽车, mState=向前开]hashCode309858374
    		System.out.println("------------------------------");
    		//使用单例模式管理汽车
    		//虽然创建了两个对象,但同时只有一个引用。
    		Car c_1 = SingletonNumOne.getInstance();
    		Car c_2 = SingletonNumOne.getInstance();
    		
    		c_1.driverBackward();
    		System.out.println(c_1);//Car [mName=小明家的汽车, mState=倒车]hashCode241990244
    		System.out.println(c_2);//Car [mName=小明家的汽车, mState=倒车]hashCode241990244
    		System.out.println("-----------------------");
    		c_2.driverForward();
    		System.out.println(c_1);//Car [mName=小明家的汽车, mState=向前开]hashCode241990244
    		System.out.println(c_2);//Car [mName=小明家的汽车, mState=向前开]hashCode241990244
    	}
    }
    

    2、SingletonNumOne.java//单例对象获取类

    public class SingletonNumOne {
    	private static Car car = null;
    	
    	public static Car getInstance(){
    		if(car == null){
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			car = new Car();
    		}
    		return car;
    	}
    }
    

    测试结果,我附在打印函数的后面了。

    这种方式成为”延迟实例化“的方式,创建单例对象,这个模式在大部分状态下都工作正常,但如果在多线程系统中,就不那么正常了。

    3、TestMultiThread.java

    public class TestMultiThread {
    	public static void main(String args[]){
    		NewThread n1 = new NewThread();
    		NewThread n2 = new NewThread();
    		new Thread(n1).start();
    		new Thread(n2).start();
    		
    		try {
    			Thread.sleep(3000);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		Car c1 = n1.getCar();
    		Car c2 = n2.getCar();
    		
    		System.out.println(c1);//Car [mName=小明家的汽车, mState=null]hashCode1667685889
    		System.out.println(c2);//Car [mName=小明家的汽车, mState=null]hashCode1987659426
    	}
    }
    class NewThread implements Runnable{
    	Car car_1;
    	@Override
    	public void run() {
    		car_1 = SingletonNumOne.getInstance();
    	}
    	public Car getCar(){
    		return car_1;
    	}
    }
    

    输出结果,我附在打印函数后面了,通过hashcode,我们可以看到,这是两个不同的对象。不是说通过单例模式得到的都是同一个对象吗?这是怎么回事?

    其实这是java同步并发引起的,试想多个线程,同时运行SingletonNumOne.getInstance()方法,每个线程在任何时刻都可能获取时间片并执行此函数,这就会产生,一个线程正在实例化new Car,另一个线程也运行到这里。

    针对这个问题,有三种解决方案。

    第一种解决方案:加同步锁

    4、SingletonNumOne.java

    public class SingletonNumOne {
    	private static Car car = null;
    	
    	public static synchronized Car getInstance(){
    		if(car == null){
    			try {
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			car = new Car();
    		}
    		return car;
    	}
    }
    

    第二种方式:使用”急切创建模式“得到单例对象

    5、SingletonNumTwo.java

    public class SingletonNumTwo {
    	private static Car car = new Car();
    	
    	public static synchronized Car getInstance(){
    		return car;
    	}
    }
    


    第三种:使用双重检查加锁的方式

    6、SingletonNumThree.java

    public class SingletonNumThree {
    	private volatile static Car mCar;
    	
    	public static Car getInstance(){
    		if(mCar == null){
    			synchronized(Car.class){
    				if(mCar == null){
    					mCar = new Car();
    				}
    			}
    		}
    		return mCar;
    	}
    }
    


    Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

    测试类:

    7、Test2.java

    
    
    public class Test2 {
    	public static void main(String args[]){
    		//第一种方式 SingletonNumOne在getInstance方法上加同步锁
    		Car car1 = SingletonNumOne.getInstance();
    		Car car2 = SingletonNumOne.getInstance();
    		
    		System.out.println(car1);//Car [mName=小明家的汽车, mState=null]hashCode613481760
    		System.out.println(car2);//Car [mName=小明家的汽车, mState=null]hashCode613481760
    		System.out.println("---------------------");
    		//第二种方法 单例模式”急切“创建法
    		Car car3 = SingletonNumTwo.getInstance();
    		Car car4 = SingletonNumTwo.getInstance();
    		
    		System.out.println(car3);//Car [mName=小明家的汽车, mState=null]hashCode1987659426
    		System.out.println(car4);//Car [mName=小明家的汽车, mState=null]hashCode1987659426
    		System.out.println("---------------------");
    		//第三种方法://双重检查加锁
    		Car car5 = SingletonNumThree.getInstance();
    		Car car6 = SingletonNumThree.getInstance();
    		
    		System.out.println(car5);//Car [mName=小明家的汽车, mState=null]hashCode2048243029
    		System.out.println(car6);//Car [mName=小明家的汽车, mState=null]hashCode2048243029
    	}
    }
    

    测试结果我附在打印函数后面了。

    总结:单件模式确保程序中一个类仅有一个实例对象,单件模式提供访问这个单件对象的全局访问点。在java中实现单件模式需要似有构造器和一个静态方法和一个静态变量。在使用单件模式的第一种方案时要处理好多线程引发的问题。


  • 相关阅读:
    springboot启动后执行某些动作
    Virtualbox的nat网络
    xshell6
    day01 K8S
    Nginx的日志文件切割
    virtualbox磁盘空间大小调整
    装修柜子木台面
    mybatis 批量in 多个字段写法
    jenkins shell常用配置
    activiti工作流引擎数据库表结构
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/2980751.html
Copyright © 2011-2022 走看看