介绍
单例模式的意图:保证某个类在系统中有且仅有一个实例。
我们可以看到下面的类图:一般的单例的实现,是属性中保持着一个自己的私有静态实例引用,还有一个私有的构造方法,然后再开放一个静态的获取实例的方法给外界获取实例对象。
代码实现
在java中有两种实现的方法
- 饿汉式:在类加载的时候就创建好实例
- 懒汉式:在请求实例时才创建实例
饿汉式
在类加载的时候就创建好实例
public class TestObj {
private static TestObj testObj=new TestObj();
//构造方法私有化
private TestObj(){
}
//提供一个外界获取单实例的静态方法
public static TestObj getTestObj(){
return testObj;
}
}
懒汉式
由于java是多线程的,很有可能多个线程同时进入,导致创建多个实例,于是我们要使用锁机制来让线程之间互斥访问
public class TestObj2 {
//初始,维护一个静态的空引用
private static TestObj2 testObj2=null;
//私有的构造方法
private TestObj2(){
}
//提供给外界获取单实例的静态方法
public static TestObj2 getTestObj2(){
//在这里,由于java是多线程的,很有可能多个线程同时进入,导致创建多个实例,于是我们要使用锁机制来让线程之间互斥访问
//多线程同时判断,如果不为null,直接返回
if (testObj2!=null){
return testObj2;
}
//使用同步代码块进行线程互斥访问
synchronized (Object.class){
//其他线程进入以后,如果已经创建好了对象,则直接返回
if (testObj2!=null){
return testObj2;
}
else {
//初始化单实例
testObj2=new TestObj2();
}
}
return testObj2;
}
}
我们这里测试一下:
创建三个线程同时去获取实例,看输出的地址是否一样
//创建一个runable接口
class DoTask implements Runnable{
@Override
public void run() {
//获取实例对象并输出
TestObj2 testObj2 = TestObj2.getTestObj2();
System.out.println(testObj2);
}
}
class Main{
public static void main(String[] args) {
Runnable runnable=new DoTask();
//创建多个线程去获取实例
Thread thread1=new Thread(runnable);
Thread thread2=new Thread(runnable);
Thread thread3=new Thread(runnable);
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
可以看到输出的三个地址都是相同的,因此我们上面的懒汉式代码是完全可行的,这里仅仅讨论单服务器的情况,如果是分布式系统中的单实例,就要考虑使用分布式锁,或者redis,zookeeper等分布式协调工具去完成了