单例设计模式的目的就是让对象只创建一次,比如在 JDK 中的 Runtime 类默认的创建方式就是使用单例模式。同时数据库连接池、线程池等等一些重量级的对象只需要创建一次随处使用即可。
单例设计模式共有 7 种创建的方式。单例的主要特点就是构造方法是私有化的。
第一种:饿汉式(线程安全)
饿汉式的方式就是类在加载之后,就立即创建一个实例对象。它是线程安全的。
public class Singleton {
public final static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
class Run {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
// 两个对象是否一样
System.out.println(singleton == singleton1);
}
}
第二种:懒汉式(线程不安全)
懒汉式的方式就是类在加载之后,只有在调用 getInstance 方法时,它才会创建对象。它在单线程下可以保证单例,但是在多线程就无法保证单例的了。
public class Singleton {
public static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
class Run {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
// 两个对象是否一样
System.out.println(singleton == singleton1);
}
}
第三种:懒汉式,同步方法(线程安全,低效率)
解决在多线程下单例的方法,简单粗暴的在 getInstance 方法加一个同步关键字,虽然可以保证线程的安全,但是效率太低了,因为每个线程调用该方法是都需要等待获得锁。
public class Singleton {
public static Singleton singleton = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();s
}
return singleton;
}
}
class Run {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
// 两个对象是否一样
System.out.println(singleton == singleton1);
}
}
第四种:懒汉式,同步代码块(线程不安全)
虽然加了同步关键字,但是一开始有多个线程访问该对象,当线程中获得的都是 null 时,则在同步代码块中等待获得锁,当第一个线程执行完 new 时,交给第二个,第二个再次 new 时,第一个线程获得的对象就和第二个线程获得的对象就不一样了。
public class Singleton {
public static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
class Run {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
// 两个对象是否一样
System.out.println(singleton == singleton1);
}
}
第五种:懒汉式,同步代码块(线程安全,效率较高)
使用双重校验就可以解决,只需要在同步代码块中再次判断对象是否已经创建即可。同时为了保证可见性,在共享的变量加一个 volatile 即可。
可见性是值一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。
为什么会出现这种问题呢?
我们知道,Java 线程通信是通过共享内存的方式进行通信的,但是为了加快执行的速度,线程一般是不会直接操作内存的,而是操作缓存。
实际上,线程操作的是自己的工作内存,而不会直接操作主内存。如果线程对变量的操作没有刷写会主内存的话,仅仅改变了自己的工作内存的变量的副本,那么对于其他线程来说是不可见的。而如果另一个变量没有读取主内存中的新的值,而是使用旧的值的话,同样的也可以列为不可见。
public class Singleton {
public static volatile Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null) {
synchronized (Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
class Run {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
// 两个对象是否一样
System.out.println(singleton == singleton1);
}
}
第六种:静态内部类(线程安全)
使用静态内部类创建单例对象也是一种不错的方式,静态的内部类不会随着外部类的加载而加载,由于类在加载的时候就是线程安全的,所以这里用的是类加载的方式来创建的。
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
private static class SingletonInstance {
private final static Singleton INSTANCE = new Singleton();
}
}
class Run {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
// 两个对象是否一样
System.out.println(singleton == singleton1);
}
}
第七种:枚举(线程安全)
使用枚举的方式也可以创建单例对象,当被反序列化的时候,第六种就无法保证对象是单例的了。Effective Java 作者推荐使用枚举的方式解决单例模式,但是感觉方式可能是平时最少用到的。
public enum Singleton {
INSTANCE;
public void ok() {
System.out.println("ok");
}
}
class Main {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance == instance1);
instance.ok();
}
}
请勿发布违反中国大陆地区法律的言论,请勿人身攻击、谩骂、侮辱和煽动式的语言。