单例设计模式的意思就是,确保一个类只能有一个对象。任何别的方法什么的对该类的操作全是对这一个对象的操作。如同我们电脑上的回收站。无论那个盘都有一个回收站的空间。但都是同一个。可以在回收站里全删除了。Jvm里面的GC也是一个意思。
单例设计模式有两种情况。
一、懒汉式
- 声明一个私有的静态变量
- 构造器私有化
- 创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象。如果存在则返回这个私有的静态变量。这样就能保证外部在使用该类的对象时只会用同一个。
基本的懒汉式
class Jvm{
//声明一个私有的静态变量
private static Jvm instance;
//构造器私有化
private Jvm(){
}
//创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象
public static Jvm getJvm(){
if(null==instance){
instance=new Jvm();
}
return instance;
}
}
这样在执行下列语句
Jvm jvm1=Jvm.getJvm();
Jvm jvm2=Jvm.getJvm();
System.out.println(jvm1);
System.out.println(jvm2);
他们得到的对象都是一样的了。地址一样即为同一个对象。
synTest.Jvm@15db9742
synTest.Jvm@15db9742
但是这样的懒汉式却是不安全的。如果是多线程来使用这个类,就会存在延迟。可能会产生多个对象。
这里我们放大他的延迟时间更容易的得到错误的结果。
修改上面的Jvm类。给他设置一个延迟时间
class Jvm1{
private static Jvm1 instance;
private Jvm1() {
}
public static Jvm1 getJvm(long time) {
if (null == instance) {
try {
Thread.sleep(time); //这里由外部给定延迟时间
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
return instance;
}
}
创建使用这个Jvm1的线程
class JvmThread extends Thread{
private long time;
public JvmThread() {
}
//为方便查看,这里构造器也传个name,给这个线程设置名字。
public JvmThread(long time,String name){
this.time=time;
//父类继承了私有的name,直接设置即可
super.setName(name);
}
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了:"+Jvm1.getJvm(this.time));
//这里直接在线程里面输出地址
}
}
执行下列语句
JvmThread jth1=new JvmThread(500,"a");
JvmThread jth2=new JvmThread(1000,"b");
jth1.start();
jth2.start();
上面两个线程都使用了Jvm1类。构造了。按照单例模式的要求,对象地址应该是一样的。但结果如下
a创建了:synTest.Jvm1@43e8772d
b创建了:synTest.Jvm1@20b708e4
地址却不一样,下面分析
所以这里就需要用到同步措施,使线程变得安全。
比较简单的方法是直接使用同步方法,或同步块
class Jvm1{
private static Jvm1 instance;
private Jvm1() {
}
//这个方法改成同步方法即可
public static synchronized Jvm1 getJvm1(long time) {
if (null == instance) {
try {
Thread.sleep(time); //这里由外部给定延迟时间
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
return instance;
}
}
或者同步块
public static Jvm1 getJvm1(long time) {
synchronized(Jvm1.class){
if (null == instance) {
try {
Thread.sleep(time); //这里由外部给定延迟时间
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
return instance;
}
}
但是这样子设置会出现一个问题,每一个线程调用这个方法都得等待,即使已经有对象了,还需要等待。
稍作改进,人称double check
public static Jvm1 getJvm1(long time) {
if(null==instance){
synchronized(Jvm1.class){
if (null == instance) {
try {
Thread.sleep(time); //这里由外部给定延迟时间
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
}
}
return instance;
}
这样子,如果线程调用时,已经有对象的时候,就可以直接返回啦。不需要进去等待。而如果多个线程同时在没有对象时调用。也会进入那个synchronized块。不会生成多个对象。
二、饿汉式
1.构造器私有化
2.声明私有的静态属性,同时创建对象
3.对外提供访问属性的静态方法
饿汉式由于对象直接构建了,所以调用都是调用那个对象,不存在线程安全问题了。
饿汉式也有普通的和完善的。
基本饿汉式
class MyJvm{
//声明私有的静态变量,同时构建对象
private static MyJvm instance=new MyJvm();
//构造器私有
private MyJvm() {
}
public static MyJvm getMyJvm(){
return instance;
}
}
这里有一点不好的就是,在声明MyJvm时候,就构造了他的对象。如果MyJvm里面还有其他的静态方法的话,并且也只使用其他方法,那么那个对象的必要性就没有了。所以做如下改进。
class MyJvm2{
//这里定义一个静态内部类。类是在调用的时候才会加载的。
//当调用了下面的getMyJvm2方法时,才会用到MyJvm2Holder类,才会加载这个类。就会有那个对象了。
private static class MyJvm2Holder{
private static MyJvm2 instance=new MyJvm2();
}
private MyJvm2() {
}
public static MyJvm2 getMyJvm2(){
return MyJvm2Holder.instance;
}
}