常用单例模式
单例模式为Java中最简单的设计模式之一。该模式的目的是用来在程序中创建唯一对象模型。
一些工具类,工厂类等服务性的类,可以考虑设置为单例模式。
单例模式的实现方式有很多,下面来一一列举。
饿汉式
一般情况下推荐使用此种方式,简单实用,且线程安全。
不算缺点的缺点:
不管是否用到,类装载时就进行实例化(一般情况下,不用你写他干啥)
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用!
* 唯一缺点:不管用到与否,类装载时就完成实例化
*/
public class Singleton01 {
private static final Singleton01 INSTANCE = new Singleton01 ();
private Singleton01 () {};
public static Singleton01 getInstance() {
return INSTANCE;
}
public void test() {
System.out.println("test");
}
public static void main(String[] args) {
Singleton01 s1 = Singleton01 .getInstance();
Singleton01 s2 = Singleton01 .getInstance();
System.out.println(s1 == s2);
}
}
懒汉式
用来解决类加载时初始化对象的问题,此方式只有在第一次调用
getInstance()
方法时才会初始化对象。
但会造成线程安全问题。(丢西瓜捡芝麻)
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
*/
public class Singleton02 {
private static Singleton02 INSTANCE;
private Singleton02 () {}
public static Singleton02 getInstance() {
if (INSTANCE == null) {
/*
//增加多线程抢占几率 更容器看到错误
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
INSTANCE = new Singleton02 ();
}
return INSTANCE;
}
public void test() {
System.out.println("test");
}
public static void main(String[] args) {
//测试线程不安全
for(int i=0; i<100; i++) {
new Thread(()->
System.out.println(Mgr03.getInstance().hashCode())
).start();
}
}
}
懒汉式-单锁
在懒汉式的基础上解决线程安全问题,但效率下降
/**
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class Singleton03 {
private static Singleton03 INSTANCE;
private Singleton03 () {}
public static synchronized Singleton03 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton03 ();
}
return INSTANCE;
}
public void test() {
System.out.println("test");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Singleton03 .getInstance().hashCode());
}).start();
}
}
}
懒汉式-双重锁判断
懒汉式-单锁版本由于在getInstance()
方法上使用 synchronized 上锁,造成效率较低。所以采取双重锁来进行效率提升
说明:
如果使用单锁缩小锁的代码,则仍存在线程不安全情况
eg.
public static Singleton04 getInstance() {
if (INSTANCE == null) {
//妄图通过减小同步代码块的方式提高效率,然后不可行
synchronized (Singleton04 .class) {
INSTANCE = new Singleton04 ();
}
}
return INSTANCE;
}
所以使用双锁来解决问题,人们也认为这个是单例模式的完美模式之一
public class Singleton04 {
private static volatile Singleton04 INSTANCE; //JIT
private Singleton04 () {
}
public static Singleton04 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton04 .class) {
//双重检查
if(INSTANCE == null) {
INSTANCE = new //双重检查();
}
}
}
return INSTANCE;
}
public void test() {
System.out.println("test");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Singleton04.getInstance().hashCode());
}).start();
}
}
}
静态内部类方式
通过使用静态内部类方式来解决延迟加载问题。
原理:加载外部类时不会加载内部类,private
修饰的内部类又只能被外部类使用,而类只会被JVM加载一次,所以是使用JVM保证单例
完美的单例模式之一
/**
* 静态内部类方式
* JVM保证单例
* 加载外部类时不会加载内部类,这样可以实现懒加载
*/
public class Singleton05{
private Singleton05() {
}
private static class Singleton05Holder {
private final static Singleton05 INSTANCE = new Singleton05();
}
public static Singleton05 getInstance() {
return Singleton05Holder .INSTANCE;
}
public void test() {
System.out.println("test");
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Singleton05 .getInstance().hashCode());
}).start();
}
}
}
枚举方式
使用枚举方式来创建单例,不仅可以解决线程同步,还可以防止反序列化。
最完美单例模式
/**
* 不仅可以解决线程同步,还可以防止反序列化。
*/
public enum Singleton06{
INSTANCE;
public void test() {
System.out.println("test")
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Singleton06.INSTANCE.hashCode());
}).start();
}
}
}
以上几种皆为JAVA中的单例模式,在日常使用中,直接使用饿汉式即可,喜欢精益求精的,可以尝试静态内部类、双重锁、枚举的方式、如果涉及到反序列化,推荐枚举方式。