一、简介

  单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

二、主要特征

  1.单例类只能有一个实例(即单例类不能被外部实例化---单例类的构造方法由private修饰)。

  2.单例类必须自己创建自己的实例,并且供外部调用(向外部暴露调用其实例的静态方法)。

三、优缺点

  1.优点:

    1)在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

    2)不需要去重复的创建实例,避免对资源的多重占用(比如写文件操作)。

  2.缺点:

    没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

四、实现

  1.场景:一个班级有很多学生,但只有一个班主任

  2.代码实现:

    1)饿汉式:即类加载时就实例化,相对浪费内存,并且容易产生垃圾对象

public class Teacher {
/**
* 创建自己的实例
*/
private static Teacher teacher = new Teacher(); /**
* 静态构造方法,不允许外部对其实例化
*/
private Teacher(){} /**
* 将自己的实例对象,提供给外部使用
* @return Teacher
*/
public static Teacher getInstance(){
return teacher;
} public void attendClass(){
System.out.println("班主任说:上课");
} public void finishClass(){
System.out.println("班主任说:下课");
}
}

    2)懒汉式:第一次调用才实例化,避免浪费内存

public class Teacher {
/**
* 创建自己的实例
*/
private static Teacher teacher; /**
* 静态构造方法,不允许外部对其实例化
*/
private Teacher(){} /**
* 将自己的实例对象,提供给外部使用
* @return Teacher
*/
public static Teacher getInstance(){
     if(null == teacher){
       teacher = new Teacher();
     }
return teacher;
} public void attendClass(){
System.out.println("班主任说:上课");
} public void finishClass(){
System.out.println("班主任说:下课");
}
}

注:懒汉式加载是否为线程安全的区别在于synchronized 关键字,给getInstance()方法加上synchronized 关键字,使得线程安全,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

    3)双检锁/双重校验锁(DCL,即 double-checked locking):采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。

public class Teacher {
/**
* 创建自己的实例
*/
private volatile static Teacher teacher = new Teacher(); /**
* 静态构造方法,不允许外部对其实例化
*/
private Teacher(){} /**
* 将自己的实例对象,提供给外部使用
* @return Teacher
*/
public static Teacher getInstance(){
if (teacher == null) {
synchronized (Teacher.class) {
if (teacher == null) {
teacher = new Teacher();
}
}
}
return teacher;
} public void attendClass(){
System.out.println("班主任说:上课");
} public void finishClass(){
System.out.println("班主任说:下课");
}
}

    4)登记式/静态内部类:与双检索方式功效一样,但实现更简单;这种方式同样在类加载时不会实例化,只有在外部调用其getInstance()方法时才会被实例化

public class Teacher {
/**
* 静态内部类
*/
private static class TeacherHolder {
/**
* 创建Teacher实例,只有在显示的调用时才会被实例化
*/
private static final Teacher INSTANCE = new Teacher();
}
/**
* 静态构造方法,不允许外部对其实例化
*/
private Teacher (){} /**
* 将单例类,提供给外部使用
* @return Teacher
*/
public static final Teacher getInstance() {
return TeacherHolder.INSTANCE;
} public void attendClass(){
System.out.println("班主任说:上课");
} public void finishClass(){
System.out.println("班主任说:下课");
}
}

    5)枚举:实现单例模式的最佳方法,更简洁,自动支持序列化机制,绝对防止多次实例化(目前未被广泛使用)

public enum Teacher {
INSTANCE;
public void whateverMethod() {
System.out.println("枚举下单例模式");
}
}

以上代码讲述了如何创建一个单例类Teacher,下面来看一下如何使用这个单例

public class Test {
public static void main(String[] args) {
//调用单例类供外部调用的方法,获取其实例
Teacher teacher = Teacher.getInstance();
teacher.attendClass();
teacher.finishClass();
}
}

这样一个简单的单例模式的例子就已经完成了,接下来举一个实际应用的例子,例如:jdbc

public class DBUtils {
public static final String URL = "jdbc:mysql://localhost:3306/demo";
public static final String USER = "root";
public static final String PASSWORD = "root";
private static Connection conn = null;
static{
try {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
} public static Connection getConnection(){
return conn;
}
}
public class TeacherDB {
private int id;
private String name; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "TeacherDB{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class TestDB {
public static boolean addTeacher() {
//获取jdbc连接
Connection connection = DBUtils.getConnection();
//sql
String sql = "INSERT INTO teacher(name) values(?)";
PreparedStatement ptmt = null;
try {
//预编译SQL,减少sql执行
ptmt = connection.prepareStatement(sql);
ptmt.setString(1,"john");
return ptmt.execute();
} catch (SQLException e) {
e.printStackTrace();
}
return false;
} public static List<TeacherDB> queryTeacher() {
//获取jdbc连接
Connection connection = DBUtils.getConnection();
//sql
String sql = "select * from teacher";
Statement stmt = null;
ResultSet rs = null;
List<TeacherDB> list = new ArrayList<>();
try {
stmt = connection.createStatement();
rs = stmt.executeQuery(sql);
while (rs.next()){
TeacherDB teacherDB = new TeacherDB();
teacherDB.setId(rs.getInt("id"));
teacherDB.setName(rs.getString("name"));
list.add(teacherDB);
}
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return list;
} public static void main(String[] args) {
System.out.println(addTeacher());
List<TeacherDB> list = queryTeacher();
list.stream().forEach(item->{
System.out.println(item);
});
}
}

五、总结

  一般情况下,不建议使用懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用登记式/静态内部类的方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁/双重校验锁的方式。本篇就到此结束了,谢谢观看

最新文章

  1. SQL Server-聚焦查询计划Stream Aggregate VS Hash Match Aggregate(二十)
  2. 深入理解Sqlserver文件存储之页和应用 (转)
  3. 如何在一个页面添加多个不同的kindeditor编辑器
  4. UILabel 根据文本内容设置frame
  5. Linux SELinux命令
  6. 使用Node.js和Redis实现push服务--转载
  7. Spark on Yarn年度知识整理
  8. js SVG
  9. hdu 携程全球数据中心建设 (球面距离 + 最小生成树)
  10. sgu Ice-cream Tycoon
  11. 关于terraform的状态管理
  12. Java中获取系统时间的四种方式
  13. CCPC2017湘潭 1263 1264 1267 1268
  14. linux 断网 扫描基本命令
  15. Obtain older GMT versions
  16. Spring在代码中获取properties文件属性
  17. configure: error: lzo enabled but missing
  18. 价格战拉上了Android平板电脑
  19. php redis中文手册
  20. Shell 变量简介

热门文章

  1. Django的F查询和Q查询,事务,ORM执行原生SQL
  2. mybatis缓存之一级缓存(二)
  3. 作为一个Java开发你用过Jib吗
  4. Jmeter(十三) - 从入门到精通 - JMeter定时器 - 上篇(详解教程)
  5. 在MFC下绘制直线,使用橡皮筋技术,可以使直线效果跟随鼠标移
  6. 安装pymysql模块及使用
  7. Java1.7的HashMap源码分析-面试必备技能
  8. java语言进阶(一)_Object类_常用API
  9. SpringBoot集成Spring Security
  10. MSIL入门(一)C#代码与IL代码对比