1、单例模式
顾名思义,单例模式能保证某个类在程序中只存在唯一一份示例,而不会创建出多个实例。就像java的JDBC编程只需要创建一个单例类DataSourece从这个DataSorce中获取数据库连接。没必要创建多个对象。
单例模式具体实现方式分为“饿汉”和“懒汉”两种。
如何保证一个程序中的单例?
1、人为约定,让大家不去new对象,给大家提供了一个方法,用的时候调用这个方法就行。
2、通过语言自身的语法限制一个类只能存在一个对象。
分析过程:
1.1、饿汉模式
代码示例:
/**
* 单例模式
*/
public class Singleton_Hungry {
//类的成员变量
private static Singleton_Hungry instance=new Singleton_Hungry();
//重写为无参构造方法,修饰符改为私有
private Singleton_Hungry() {
}
//对外提供一个获取成员变量的方法
public static Singleton_Hungry getInstance(){
return instance;
}
}
public class Exe_01 {
public static void main(String[] args) {
Singleton_Hungry singleton1=Singleton_Hungry.getInstance();
Singleton_Hungry singleton2=Singleton_Hungry.getInstance();
Singleton_Hungry singleton3=Singleton_Hungry.getInstance();
Singleton_Hungry singleton4=Singleton_Hungry.getInstance();
//打印类对象
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton3);
System.out.println(singleton4);
}
}
1.2、懒汉模式
1.2.1、单线程版
代码示例:
public class Singleton_Lazy {
private static Singleton_Lazy instance=null;
private Singleton_Lazy(){
}
public static Singleton_Lazy getInstance(){
//判断返回的对象是否为空
if(instance==null){
//创建对象
instance=new Singleton_Lazy();
}
//返回单例对象
return instance;
}
}
public class Exe_03 {
public static void main(String[] args) {
//让多线程并发获取单例对象
for (int i = 0; i < 10; i++) {
Thread thread=new Thread(() ->{
//获取单例
Singleton_Lazy instance=Singleton_Lazy.getInstance();
System.out.println(instance);
});
//启动线程
thread.start();
}
}
}
1.2.2、多线程版
上面的懒汉模式-单线程版的实现是不安全的。
线程安全问题发生在首次创建实例时,如果在多个线程中同时调用getInstance方法,就可能导致创建出多个实例。
一旦实例已经创建好了,后面的多线程环境调用getInstance就不再有线程安全问题了(不再修改instance)。
加上synchronized可以改善这里的线程安全问题
public static Singleton_DCL getInstance() { synchronized (Singleton_DCL.class) { //判断返回的对象是否为空 //第二次判断,如果为空重新创建对象 if (instance == null) { //创建对象 instance = new Singleton_DCL(); } } //返回单例对象 return instance; }
代码示例:
public class Singleton_Lazy {
private static Singleton_Lazy instance=null;
private Singleton_Lazy(){
}
public static Singleton_Lazy getInstance(){
synchronized(Singleton_Lazy.class) {
//判断返回的对象是否为空
if (instance == null) {
//创建对象
instance = new Singleton_Lazy();
}
}
//返回单例对象
return instance;
}
}
public class Exe_03 {
public static void main(String[] args) {
//让多线程并发获取单例对象
for (int i = 0; i < 10; i++) {
Thread thread=new Thread(() ->{
//获取单例
Singleton_Lazy instance=Singleton_Lazy.getInstance();
System.out.println(instance);
});
//启动线程
thread.start();
}
}
}
1.2.3、双重检查锁(Double Check Locker)DCL
多线程版的单例模式还存在一个严重的问题:
1、当变量没有被初始化的时候,第一次创建可能会出现线程安全问题,因为多个 线程都会创建实例对象。
2、一旦实例对象被创建之后,new操作就永远不会执行了,因为获取到的实例不为null。
3、那么这时就不需要去执行加锁操作,而且加锁也是要去系统申请锁资源,如果拿到锁之后只是判断一下实例对象是否为null,如果为null什么也不干,那么锁资源也就拜拜浪费掉了
所以就出现了双重校验锁,线程安全的单例模式
代码示例:
public class Singleton_DCL {
private volatile static Singleton_DCL instance=null;
private Singleton_DCL(){
}
public static Singleton_DCL getInstance(){
//第一次判断是否为空,目的是判断是否需要加锁
if(instance==null) {
synchronized (Singleton_DCL.class) {
//判断返回的对象是否为空
//第二次判断,如果为空重新创建对象
if (instance == null) {
//创建对象
instance = new Singleton_DCL();
}
}
}
//返回单例对象
return instance;
}
}
public class Exe_04 {
public static void main(String[] args) {
//让多线程并发获取单例对象
for (int i = 0; i < 10; i++) {
Thread thread=new Thread(() ->{
//获取单例
Singleton_DCL instance=Singleton_DCL.getInstance();
System.out.println(instance);
});
//启动线程
thread.start();
}
}
}