设计模式之单例模式
单例模式:确保一个类只有一个实例,并提供一个全局的访问点。
在单例模式下,当需要返回单个实例时,通过单件类获取是唯一的途径。
情景:小明家只有一辆车,车在某一个时刻,只有一个状态,要么前进,要么后退,也就是倒车。
案例代码:
在正规的单例模式中,单例类需要提供似有的构造方法,通过共有的全局访问点。在本测试代码中为了比较差异,对单例模式稍作改动。
单例模式只允许创建一个对象,为了检查是不是一个对象,我把对象的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中实现单件模式需要似有构造器和一个静态方法和一个静态变量。在使用单件模式的第一种方案时要处理好多线程引发的问题。

浙公网安备 33010602011771号