单例模式是我们最常用的设计模式,但是你真的懂单例吗?
- 饿汉式与懒汉式的区别?
- 线程安全怎么解决?
- 能防止反序列化吗?
首先说一下什么是单例模式
这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,
- 单例就是单一、独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?
- 我们举个比较难复制的对象:皇帝
中国的历史上很少出现两个皇帝并存的时期,是有,但不多,那我们就认为皇帝是个单例模式,在这个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样(过渡期的不考虑,别找茬哦),大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,单例模式,绝对的单例模式, - 先看类图:
简单应用
先创建一个皇帝类
public class Emperor {
private static Emperor emperor = null; //定义一个皇帝放在那里,然后给这个皇帝名字
private Emperor() {
//世俗和道德约束你,目的就是不让你产生第二个皇帝
}
public static Emperor getInstance(){
if (emperor == null){
emperor = new Emperor();
}
return emperor;
}
public static void emperorInfo(){
System.out.println("我就是皇帝武则天....");
}
}
再创建一个大臣类
public class Minister {
public static void main(String[] args) {
//第一天
Emperor instance = Emperor.getInstance();
Emperor emperor = instance;
emperor.emperorInfo();//第一天见的皇帝叫什么名字呢?
//第一天
Emperor instance2 = Emperor.getInstance();
instance2.emperorInfo();
//第三天
Emperor instance3 = Emperor.getInstance();
instance3.emperorInfo();
}
}
运行结果
我就是皇帝武则天....
我就是皇帝武则天....
我就是皇帝武则天....
这是单例模式的最简单应用
进阶
- 饿汉式
缺点:不管是否使用,类装载完成时,一定会完成实例化
/**
* @author Administrator
* @version 1.0
* @description: TODO 单例模式
* @date 2021-8-23 下午 10:28
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用
* 缺点:不管是否使用,类装载完成时,一定会完成实例化
*/
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01(){}
public static Mgr01 getInstance(){
return INSTANCE;
}
public static void m(){
System.out.println("M");
}
public static void main(String[] args) {
Mgr01 mgr01 = Mgr01.getInstance();
Mgr01 mgr02 = Mgr01.getInstance();
System.out.println(mgr01 == mgr02);
}
}
饿汉式的另一种写法
/**
* @author Administrator
* @version 1.0
* @description: TODO 01的另一种写法
* @date 2021-8-23 下午 10:38
*
*/
public class Mgr02 {
private static final Mgr02 INSTANCE ;
static {
INSTANCE = new Mgr02();
}
public static Mgr02 getInstance(){
return INSTANCE;
}
public static void m(){
System.out.println("M");
}
public static void main(String[] args) {
Mgr02 mgr01 = Mgr02.getInstance();
Mgr02 mgr02 = Mgr02.getInstance();
System.out.println(mgr01 == mgr02);
}
}
- 懒汉式
优点:按需初始化
缺点:线程不安全
/**
* @author Administrator
* @version 1.0
* @description: TODO
* @date 2021-8-23 下午 10:40
* lazy loading 懒汉式
* 虽然达到了按需初始化的目的,但却带来了线程不安全的问题
*/
public class Mgr03 {
private static Mgr03 INSTANCE ;
private Mgr03(){}
public static Mgr03 getInstance(){
if (INSTANCE == null){
try{
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public static void m(){
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Mgr03.getInstance().hashCode());
}).start();
}
}
}
- 使用synchronized解决线程不安全问题
缺点:效率下降
/**
* @author Administrator
* @version 1.0
* @description: TODO
* @date 2021-8-23 下午 10:40
* lazy loading 懒汉式
* 虽然达到了按需初始化的目的,但却带来了线程不安全的问题
* 可以通过synchronized关键字来解决,但是效率会下降
*/
public class Mgr04 {
private static Mgr04 INSTANCE ;
private Mgr04(){}
public static synchronized Mgr04 getInstance(){
if (INSTANCE == null){
try{
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
public static void m(){
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
}
/**
* @author Administrator
* @version 1.0
* @description: TODO
* @date 2021-8-23 下午 10:40
* lazy loading 懒汉式
* 虽然达到了按需初始化的目的,但却带来了线程不安全的问题
* 可以通过synchronized关键字来解决,但是效率会下降
*/
public class Mgr05 {
private static Mgr05 INSTANCE;
private Mgr05() {
}
public static Mgr05 getInstance() {
if (INSTANCE == null) {
//试图通过减小同步代码块的方式提交效率,然而不可行,判断
synchronized (Mgr05.class) {
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
return INSTANCE;
}
public static void m() {
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mgr05.getInstance().hashCode());
}).start();
}
}
}
- 试图通过静态代码块的方式提高效率,但是需要双重检测
/**
* @author Administrator
* @version 1.0
* @description: TODO
* @date 2021-8-23 下午 10:40
* lazy loading 懒汉式
* 虽然达到了按需初始化的目的,但却带来了线程不安全的问题
* 可以通过synchronized关键字来解决,但是效率会下降
*/
public class Mgr06 {
private static volatile Mgr06 INSTANCE;
private Mgr06() {
}
public static Mgr06 getInstance() {
if (INSTANCE == null) {
//试图通过减小同步代码块的方式提交效率,然而不可行,判断
synchronized (Mgr06.class) {
//双重检测
if(INSTANCE == null){
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mgr06.getInstance().hashCode());
}).start();
}
}
}
/**
* @author Administrator
* @version 1.0
* @description: TODO 静态内部类方式
* JVM保证单例,加载外部类时不会加载内部类,这样可以实现懒加载
* @date 2021-8-24 上午 8:40
*/
public class Mgr07 {
private Mgr07(){
}
private static class Mgr07Holder{
private final static Mgr07 INSTANCE = new Mgr07();
}
private static Mgr07 getInstance(){
return Mgr07Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
- 枚举单例
不仅可以解决线程同步,还能防止反序列化,完美写法,线程安全由JVM负责
/**
* @author Administrator
* @version 1.0
* @description: TODO
* @date 2021-8-24 上午 8:47
* 不仅可以解决线程同步,还能防止反序列化,完美写法,线程安全由JVM负责
*/
public enum Mgr08 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Mgr08.INSTANCE.hashCode());
}).start();
}
}
}