单例模式
单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
核心代码:1构造方法私有化, 2. private static 3.提供一个公共方法
1.饿汉式
饿汉式,从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,调用效率高.坏处是浪费内存空间 无法延时加载。
public class SingletonDemo2 {
// 类初始化时 立即加载这个对象 。 加载类时是一个天然安全的模式
private static SingletonDemo2 instance = new SingletonDemo2();
private SingletonDemo2() {
}
// 方法没有同步 调用效率高
public static SingletonDemo2 getInstance() {
return instance;
}
}
2.懒汉式
懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。
优点:线程安全 延时加载 资源利用率高 缺点: 并发效率低
public class SingletonDemo1 {
// 类初始化不初始化这个对象 。延时加载 真正用的时候加载
private static SingletonDemo1 instance;
private SingletonDemo1() {//私有化构造器
}
// 方法同步 调用效率低
public static synchronized SingletonDemo1 getInstance() {
if(null==instance) {
instance = new SingletonDemo1();
}
return instance;
}
}
3.双重检测锁
双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
问题:由于编译器优化原因和jvm底层内部模型原因 偶尔会出现问题
解决方法:对之前代码加入volatile关键字,即可实现线程安全的单例模式。
public class SingletonDemo3 {
// 类初始化时 立即加载这个对象 。 加载类时是一个天然安全的模式
private static volatile SingletonDemo3 instance = null;
private SingletonDemo3() {
}
// 方法没有同步 调用效率高
public static SingletonDemo3 getInstance() {
if (instance == null) {
synchronized (SingletonDemo3.class) {
if (instance == null) {
instance = new SingletonDemo3();
}
}
}
return instance;
}
}
4.静态内部类
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式线程安全 调用效率高 实现了 延时加载
public class SingletonDemo4 {
private static class SingletonClassInstance {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4() {
}
public static SingletonDemo4 getInstance() {
return SingletonClassInstance.instance;
}
}
5.枚举
枚举的方式是比较少见的一种实现方式,并且她还自动支持序列化机制,绝对防止多次实例化。
枚举实现刊例模式 没有延时加载
- 通过Singleton.INSTANCE进行操作
public enum SingletonDemo5 {
//这个枚举元素 本身就是单例
INSTANCE;
//添加自己需要的操作
public void singletonOperation() {
}
}
好了,上面就是单例模式的五种主要写法。我们来总结下,一般情况下,懒汉式(包含线程安全和线程不安全梁总方式)都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;在要明确实现 lazy loading 效果时,可以考虑静态内部类的实现方式;若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式。
6.防止反射破解
Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.sxt.singleton.SingletonDemo6");
//获得无参构造器
Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true); //接触安全模式
SingletonDemo6 s3 = c.newInstance();
SingletonDemo6 s4 = c.newInstance();
System.out.println(s3);
System.out.println(s4);
解决办法
// 类初始化不初始化这个对象 。延时加载 真正用的时候加载
private static SingletonDemo6 instance;
private SingletonDemo6() {//私有化构造器
if(instance!=null) {//防止反射破解
throw new RuntimeException();
}
}
// 方法同步 调用效率低
public static synchronized SingletonDemo6 getInstance() {
if(null==instance) {
instance = new SingletonDemo6();
}
return instance;
}
如果存在一个对象 报异常
7.防止反序列化破解
//通过反序列化的方式构造多个对象
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
System.out.println(s3);
解决办法
//反序列化时 如果有一个readResolve方法则直接返回此方法指定的对象 而不需要单独在创建对象
private Object readResolve() throws Exception{
return instance;
}
8测试效率
public class Client3 {
public static void main(String[] args) throws Exception {
long start =System.currentTimeMillis();
int threadNum=10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingletonDemo1.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//main线程阻塞 知道计数器变为0 才会继续执行
long end =System.currentTimeMillis();
System.out.println("总耗时:"+(end-start));
}
}
借助CountDownLatch类 countDownLatch.countDown(); 计数器作用countDownLatch.await();//main线程阻塞 直到计数器变为0 才会继续执行