单例模式是指一个类,只能创建一个对象实例
本文使用了4种方式实现单例模式并逐一介绍其特点,分别为饿汉式,懒汉式,双重检索式和静态内部类式。
一、饿汉式
实现饿汉式单例模式分为三步:
1、构造器要私有化
2、本类的对象作为本类的属性存在,用static修饰,保证只加载一次,不会重复创建对象
3、公共的静态的方法,可以通过该方法,拿到成员属性
代码如下:
public class Single1 {
static int i = 1;
static Single1 s = new Single1();
private Single1() {
System.out.println("Single1的构造器");
}
public static Single1 getObject() {
return s;
}
}
public class TestSingle1 {
public static void main(String[] args) {
//此时仅仅反问Single1类的i属性,都会创建对象
System.out.println(Single1.i);
System.out.println(Single1.getObject());
System.out.println(Single1.getObject());
System.out.println(Single1.getObject());
}
}
结果为:
创建对象
1
cn.design.single.Single1@6d06d69c
cn.design.single.Single1@6d06d69c
cn.design.single.Single1@6d06d69c
饿汉式在类创建的同时就已经创建好一个静态的对象,以后不再改变,所以是安全的;但是,如果想访问成员变量i,此时也会创建一个对象,所以会导致对象创建过早,造成浪费。
二、懒汉式
实现饿汉式单例模式分为三步:
1、构造器私有化
2、本类的对象作为本类的守护星存在,static修饰,但不进行初始化
3、公共的静态的方法,并判断是否给属性赋值
public class Single2 {
public static int i = 1;
private static Single2 s;
public static Single2 getObj() {
if (s == null) {
s = new Single2();
}
return s;
}
private Single2() {
System.out.println("Single2的构造器!");
}
}
public class TestSingle2 {
public static void main(String[] args) {
//懒汉式时反问属性i不会创建对象
System.out.println(Single2.i);
Single2 t1 = Single2.getObj();
Single2 t2 = Single2.getObj();
System.out.println(t1 == t2);
}
}
结果为:
1
Single2的构造器!
true
懒汉式中对象总是在被需要的时候才创建,解决了对象可能加载时机过早的问题;但是懒汉式的线程是不安全,因为如果一个线程执行getObj()方法创建一个对象时,另外一个线程也可以该方法创建对象,下面对懒汉式的线程不安全问题进行测试,增加一个类实现Runnable接口
public class GetThread implements Runnable{
public Set<Single2> set = new HashSet<>();
@Override
public void run() {
// TODO Auto-generated method stub
Single2 s = Single2.getObj();
set.add(s);
System.out.println(set);
}
}
使用Set集合对创建的s对象进行添加,如果set中能够添加进多个元素,就说明创建了多个Single2的对象。
测试类:
public class TestThread {
public static void main(String[] args) {
GetThread gt = new GetThread();
new Thread(gt).start();
new Thread(gt).start();
new Thread(gt).start();
}
}
测试结果:
Single2的构造器!
Single2的构造器!
Single2的构造器!
[cn.design.TestSingleThread.Single2@6019722a, cn.design.TestSingleThread.Single2@4de9fa9c, cn.design.TestSingleThread.Single2@2b63218b]
[cn.design.TestSingleThread.Single2@6019722a, cn.design.TestSingleThread.Single2@4de9fa9c, cn.design.TestSingleThread.Single2@2b63218b]
[cn.design.TestSingleThread.Single2@6019722a, cn.design.TestSingleThread.Single2@4de9fa9c, cn.design.TestSingleThread.Single2@2b63218b]
可以看到set中存储了由三个线程分别创建的Single2对象,所以懒汉式在多线程的情况下是不安全的
三、双重检索式
双重检索式在懒汉式的基础上进行了改进,添加了synchronized同步代码块
public class Single3 {
public static int i;
private static Single3 s;
private Single3(){
System.out.println("Single3的构造器!");
}
public static Single3 getObj(){
if(s == null){
synchronized (Single3.class) {
if(s == null){
s = new Single3();
}
}
}
return s;
}
}
public class Single3Thread implements Runnable {
Set<Single3> set = new HashSet<>();
@Override
public void run() {
// TODO Auto-generated method stub
Single3 s = Single3.getObj();
set.add(s);
System.out.println(set);
}
}
public class TestSingle3 {
public static void main(String[] args) {
Single3Thread st = new Single3Thread();
System.out.println(Single3.i);
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
}
}
运行结果:
0
Single3的构造器!
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
可以看出双重检锁式是线程安全的,而且不存在饿汉式中对象创建过早的问题;但是双重检锁式并不能保证它会在单处理器或多处理器计算机上顺利运行,双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。
四、静态内部类式
1、定义一个静态内部类2、外部类构造器私有化
3、外部类的对象作为内部类的属性存在,static修饰,进行初始化
4、公共的静态的方法
public class Single4 {
public static int i = 1;
static class InnerClass{
private static Single4 s = new Single4();
public static Single4 getObj(){
return s;
}
}
public Single4(){
System.out.println("Single4的构造器");
}
}
public class Single4Thread implements Runnable{
Set<Single4> set = new HashSet<>();
@Override
public void run() {
// TODO Auto-generated method stub
Single4 s = Single4.InnerClass.getObj();
set.add(s);
System.out.println(set);
}
}
public class TestSingle4 {
public static void main(String[] args) {
System.out.println(Single4.i);
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
Single4Thread st = new Single4Thread();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
}
}
测试结果:
1
Single4的构造器
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
可以看出静态内部类式也是线程安全的,而且不存在饿汉式中对象创建过早的问题,静态内部类可以访问其外部类的成员属性和方法,同时,静态内部类只有当被调用的时候才开始首次被加载,因此推荐使用静态内部类的方法实现单例模式。