JAVA设计模式(二)-单例模式

本文最后更新于:April 11, 2022 pm

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

目录

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。

单例模式有八种方式:

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

饿汉式

饿汉式(静态常量)

步骤:

  • 构造器私有化,防止外部new。
  • 类内部创建对象实例。
  • 提供一个公有静态方法,返回实例对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NewJavaTest {
public static void main(String[] args) {
Bank bank1 = Bank.getOl();
Bank bank2 = Bank.getOl();
System.out.println( bank1 == bank2 ); //true
System.out.println(b1.hashCode()); //311314112
System.out.println(b2.hashCode()); //311314112 hashcode是一样的

}

}
class Bank{
private Bank(){
}
private static Bank ol = new Bank();
public static Bank getOl(){
return ol;
}
}

优缺点

  • 优点:这种写法比较简单,在类装载时就完成了实例化。避免了线程同步问题。
  • 缺点:因为在类装载时就完成了实例化。如果从始至终都从未使用过这个实例,则会造成内存的浪费。

饿汉式(静态代码块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.sjms;

/**
* @Author DragonOne
* @Date 2022/4/11 18:29
* @墨水记忆 www.tothefor.com
*/
public class testDay01 {
public static void main(String[] args) {
Bank b1 = Bank.getOl();
Bank b2 = Bank.getOl();
System.out.println(b1==b2);//true
System.out.println(b1.hashCode());
System.out.println(b2.hashCode());
}
}
class Bank{
private static Bank ol;
static {
ol = new Bank();
}
private Bank(){
}
public static Bank getOl(){
return ol;
}
}

总结

两种都可以使用,但是会造成内存的浪费。

懒汉式

线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.sjms;

/**
* @Author DragonOne
* @Date 2022/4/11 18:29
* @墨水记忆 www.tothefor.com
*/
public class testDay01 {
public static void main(String[] args) {
Bank b1 = Bank.getOl();
Bank b2 = Bank.getOl();
System.out.println(b1==b2); //true
System.out.println(b1.hashCode());
System.out.println(b2.hashCode());
}
}
class Bank{
private Bank(){
}
private static Bank ol = null;
public static Bank getOl(){ //不同点
if(ol == null) ol = new Bank();
return ol;
}
}

优缺点

  • 优点:起到了懒加载的效果。
  • 缺点:只能在单线程下使用。如果是在多线程下,在一个线程进入了if判断语句块中,这时还未向下执行时(只是单纯进入了if),另一个线程也通过了if判断语句,也进入了if中。这时便会产生多个实例。所以在多线程环境下不可使用。

在实际的开发中,也不使用这种方式。

线程安全(同步方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NewJavaTest {
public static void main(String[] args) {
Bank b1 = Bank.getOl();
Bank b2 = Bank.getOl();
System.out.println(b1 == b2);

}

}
class Bank{
private Bank(){

}
private static Bank ol = null;
public static synchronized Bank getOl(){ //不同点
if(ol == null) ol = new Bank();
return ol;
}
}

优缺点

  • 优点:解决了线程不安全问题。
  • 缺点:效率太低。

在实际开发中,不推荐使用。

线程安全(同步代码块)

实际是并不安全的。知道有这种方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.sjms;

/**
* @Author DragonOne
* @Date 2022/4/11 18:29
* @墨水记忆 www.tothefor.com
*/
public class testDay01 {
public static void main(String[] args) {
Bank b1 = Bank.getOl();
Bank b2 = Bank.getOl();
System.out.println(b1==b2);
System.out.println(b1.hashCode());
System.out.println(b2.hashCode());
}
}
class Bank{
private Bank(){
}
private static Bank ol = null;
public static Bank getOl(){ //不同点
if(ol == null) {
synchronized (Bank.class){
ol = new Bank();
}
}
return ol;
}
}

饿汉式和懒汉式的区别

懒汉式相较于饿汉式,延迟对象的创建,对于饿汉式来说就是对象加载时间过长。
饿汉式是线程安全的,懒汉式有安全和不安全的。

双重检查

推荐使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.sjms;

/**
* @Author DragonOne
* @Date 2022/4/11 18:29
* @墨水记忆 www.tothefor.com
*/
public class testDay01 {
public static void main(String[] args) {
Bank b1 = Bank.getOl();
Bank b2 = Bank.getOl();
System.out.println(b1==b2);
System.out.println(b1.hashCode());
System.out.println(b2.hashCode());
}
}
class Bank{
private Bank(){
}
private static volatile Bank ol;
public static Bank getOl(){ //不同点
if(ol == null) {
synchronized (Bank.class){
if(ol==null){
ol = new Bank();
}
}
}
return ol;
}
}

线程安全;延迟加载;效率较高。

在实际开发中,推荐使用。

静态内部类

这种方式采用了类加载的机制来保证初始化实例时只有一个线程。静态内部类方式在Bank类被装载时并不会立即实例化,而是在需要实例化时,调用getOl方法,才会装载BankInfo类,从而完成Bank类的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.sjms;

/**
* @Author DragonOne
* @Date 2022/4/11 18:29
* @墨水记忆 www.tothefor.com
*/
public class testDay01 {
public static void main(String[] args) {
Bank b1 = Bank.getOl();
Bank b2 = Bank.getOl();
System.out.println(b1==b2);
System.out.println(b1.hashCode());
System.out.println(b2.hashCode());
}
}
class Bank{
private Bank(){
}
public static class BankInfo{
private static final Bank ol = new Bank();
}
public static Bank getOl(){
return BankInfo.ol;
}
}

优缺点

  • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

开发中也比较推荐使用。

枚举

推荐使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.sjms;

/**
* @Author DragonOne
* @Date 2022/4/11 18:29
* @墨水记忆 www.tothefor.com
*/
public class testDay01 {
public static void main(String[] args) {
Bank b1 = Bank.B;
Bank b2 = Bank.B;
System.out.println(b1==b2); //true
System.out.println(b1.hashCode());
System.out.println(b2.hashCode());

}
}
enum Bank{
B; //属性
public void show(){
System.out.println("bank");
}
}

源码应用

java.lang.Runtime 中就使用到了单例模式。饿汉式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//java.lang.Runtime源码

public class Runtime {
private static Runtime currentRuntime = new Runtime();

/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}

/** Don't let anyone else instantiate this class */
private Runtime() {}
}

应用场景

使用场景:需要频繁的进行创建和销毁的对象、创建对象时比较耗时或比较耗费资源,但又是经常使用到的对象、工具类对象、频繁访问数据或文件的对象。