《java常用设计模式之----单例模式》
一、简介
单例模式(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() 的性能对应用程序很关键。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()方法时才会被实例化
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 + '\'' + '}'; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 效果时,才会使用登记式/静态内部类的方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁/双重校验锁的方式。本篇就到此结束了,谢谢观看