23种设计模式之单例模式
单例模式:
属于一种常见的创建者模式。通过单例模式创造的类只存在一个实例对象
简单的来说,某个程序的配置信息存放在一个文件中,这些配置信息由单例对象读取,然后通过单例对象服务其他对象
它的特点总共有三个:
- 构造方法私有化
- 提供一个私有的静态变量
- 有一个返回实例对象的公共方法
单例模式又有两个方法进行创建:饿汉式和懒汉式
这里用饿汉式的静态变量方法举例
public class SingletonTest {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
// 饿汉式——静态变量
class Singleton {
// 1、提供一个静态的私有的变量
private static Singleton instance = new Singleton();
// 2、构造方法私有化
private Singleton(){}
// 3、提供一个返回单例对象本身的公共方法
public static Singleton getInstance() {
return instance;
}
}
true
输出为true,表示该两个变量都是同一个地址。
饿汉式的另一种方法:静态代码块
public class SingletonTest2 {
public static void main(String[] args) {
Singleton1 instance1 = Singleton1.getInstance();
Singleton1 instance2 = Singleton1.getInstance();
System.out.println(instance1 == instance2);
}
}
// 饿汉模式——静态代码块
class Singleton1 {
private static Singleton1 instance;
static {
instance = new Singleton1();
}
private Singleton1(){}
public static Singleton1 getInstance() {
return instance;
}
}
使用多线程判断饿汉式是否存在线程安全问题
public static void main(String[] args) {
new Thread(new test_no(),"线程A").start();
new Thread(new test_no(),"线程B").start();
new Thread(new test_no(),"线程A").start();
new Thread(new test_no(),"线程B").start();
new Thread(new test_no(),"线程A").start();
new Thread(new test_no(),"线程B").start();
new Thread(new test_no(),"线程A").start();
new Thread(new test_no(),"线程B").start();
}
@SneakyThrows
@Override
public void run() {
Singleton1 singleton= Singleton1.getInstance();
System.out.println(singleton);
}
原理就在于:饿汉式是在这个程序加载的时候就已经创建好了这一个对象。所以每一个线程创建的对象都是同一个对象。
相对的,懒汉式就存在线程安全问题。
private static Singleton1 instance;
private Singleton1(){}
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
出现这个问题就在于线程的抢占,假如A进入了判断条件(if (instance==null) )就被B抢占
,B在完整的运行完整个代码块后就已经创建了一个instance对象,A在运行时,任然会执行 instance=new Singelton1()的方法,这样就存在了两个instance对象。
解决方案1:
使用synchronized关键字进行加锁。
class Singleton{
private static Singleton singleton;
private Singleton(){};
public synchronized static Singleton getInstance(){
if(singleton == null){
singleton=new Singleton();
}
return singleton;
}
}
这种方法虽然解决了线程安全问题,但是如果在getInstance方法内还存在一些不可能存在线程安全的方法,这个操作就是没必要的,所以就对存在线程安全的代码块进行加锁。
class Singleton1 {
private static Singleton1 instance;
private Singleton1(){}
public static Singleton1 getInstance() {
synchronized(Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
return instance;
}
}
这样的方法效率还是低,原因在于假如A进入了getInstance方法,B就只能在这方法之外等待A完成getInstance方法,A完成之后,B在进入getInstance方法,就没必要在进行判断的操作,所以对此就有了优化方案:(双检索)
class Singleton1 {
private static Singleton1 instance;
private Singleton1(){}
public static Singleton1 getInstance() {
if (instance == null) {
synchronized(Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
}
除此之外懒汉式还有其他的方法
内部类实现
class Singleton1 {
private static Singleton1 instance;
private Singleton1(){}
// 内部类
private static class SingletonHolder {
private static Singleton1 INSTANCE = new Singleton1();
}
public static Singleton1 getInstance() {
if (instance == null) {
instance = SingletonHolder.INSTANCE;
}
return instance;
}
}
enum实现
enum SingletonEnum {
Instance;//enum 的所有的枚举值都是public static final类型
private SingletonEnum() {
System.out.println("枚举类被实例化");
}
final static Properties prop = new Properties();
public static Properties getProperties() {
prop.setProperty("driver", "com.mysql.jdbc.Driver");
prop.setProperty("user", "root");
return prop;
}
}