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中实现单件模式需要似有构造器和一个静态方法和一个静态变量。在使用单件模式的第一种方案时要处理好多线程引发的问题。


  • 相关阅读:
    yocto/bitbake 学习资源
    QEMU/KVM学习资源
    ubuntu 中创建和删除用户
    git 重命名本地和远程分支
    Ubuntu 上搭建 FTP 服务器
    gdb 常见用法
    git log 显示与特定文件相关的 commit 信息
    基于 qemu system mode 运行 arm 程序
    基于 qemu user mode 运行 aarch64 程序
    checking in(airport)
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/2980751.html
Copyright © 2011-2022 走看看