什么是单例模式?
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。这样可以减少系统对资源的开销。如多个模块使用同一个数据源连接对象等等
为什么要用单例
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何实现单例
定义一个全局变量可以确保对象随时都可以被访问,同时能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法
第一种 懒汉模式
懒汉模式 单例类
package singletion;
import java.util.concurrent.TimeUnit;
/**
* 单例模式 之 懒汉模式
* 什么是懒汉模式
* 懒汉模式是指 当实例需要时 该实例才会被实例化, 减少系统启动内存开销, 与之对应的是饿汉模式
*/
public class SingletionLazy {
/**
* 构造方法私有化可以保证外部类无法通过new关键字来实例化
*/
private SingletionLazy(){
/**
* 如果测试结果打印了多次, 表示构造方法被多次调用,
* 说明出现线程安全问题
*/
System.out.println("是否发生线程安全问题");
}
/**
* 该属性为当前类的自身, 初始值为null,也就是说该属性会在
* 我们初次(其实并不一定只是初次,这就是线程安全问题)
* 调用getSingletionLazy方法的时候进行重新赋值,private
* 关键字保证了该对象无法通过 类名.属性名被直接访问到,
* 所以我们只能通过getSingletionLazy方法获取该类的实例
* static 关键字保证了 该属性可以被getSingletionLazy方法
* 访问到, 因为getSingletionLazy方法是static的
*/
private static SingletionLazy singletionLazy = null;
/**
* 获取该类对象的唯一入口
* @return
*/
public static SingletionLazy getSingletionLazy(){
/**
* 通过判断singletionLazy是否为null 来决定是否创建实例
*/
if(singletionLazy == null){
try {
/**
* 让当前线程休息5s。
*/
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
singletionLazy = new SingletionLazy();
}
return singletionLazy;
}
}
懒汉模式测试类
import singletion.SingletionLazy;
public class SingletionLazyTest {
public static void main(String[] args) {
/**
* 创建100个线程,查看实例化的结果
*/
for(int i = 0; i<100; i++){
new Thread() {
@Override
public void run() {
SingletionLazy singletionLazy = SingletionLazy.getSingletionLazy();
}
}.start();
}
}
}
测试结果
是否发生线程安全问题
是否发生线程安全问题
是否发生线程安全问题
是否发生线程安全问题
是否发生线程安全问题
是否发生线程安全问题
可以发现测试结果非常感人,我们并没有达到为什么想要的效果, 所以懒汉模式在多线程的情况下并不能保证单例问题。至于为什么会这样,这里暂时就不解释了,可以翻看一下线程的基础问题, 我想很快就可以得到答案。
第二种 懒汉模式加锁
package singletion;
import java.util.concurrent.TimeUnit;
/**
* 单例模式 之 懒汉模式同步
* 通过synchronized关键字来保证SingletionLazySynchronized方法
* 在同一时间只能被一个线程访问,保证了线程安全,然而也就是因为
* 这个原因 造成了该线程后面的线程发生阻塞, 只能等待该线程访问
* 结束,所以效率实分低下。
*/
public class SingletionLazySynchronized {
/**
* 构造方法私有化可以保证外部类无法通过new关键字来实例化
*/
private SingletionLazySynchronized(){
/**
* 如果测试结果打印了多次, 表示构造方法被多次调用,
* 说明出现线程安全问题
*/
System.out.println("是否发生线程安全问题");
}
/**
* 该属性为当前类的自身, 初始值为null,也就是说该属性会在
* 我们初次(其实并不一定只是初次,这就是线程安全问题)
* 调用getSingletionLazy方法的时候进行重新赋值,private
* 关键字保证了该对象无法通过 类名.属性名被直接访问到,
* 所以我们只能通过getSingletionLazy方法获取该类的实例
* static 关键字保证了 该属性可以被getSingletionLazy方法
* 访问到, 因为getSingletionLazy方法是static的
*/
private static SingletionLazySynchronized singletionLazy = null;
/**
* 获取该类对象的唯一入口
* synchronized 关键字可以保证该方法在同一时间只能被一个线程访问,
* 只有当一个线程访问结束后,另一个线程才可以访问该方法
* @return
*/
public static synchronized SingletionLazySynchronized getSingletionLazySynchronized(){
/**
* 通过判断singletionLazy是否为null 来决定是否创建实例
*/
if(singletionLazy == null){
try {
/**
* 让当前线程休息5s。
*/
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
singletionLazy = new SingletionLazySynchronized();
}
return singletionLazy;
}
}
测试类
package test;
import singletion.SingletionLazySynchronized;
public class SingletionLazySynchronizedTest {
public static void main(String[] args) {
/**
* 创建100个线程,查看实例化的结果
*/
for(int i = 0; i<100; i++){
new Thread() {
@Override
public void run() {
SingletionLazySynchronized singletionLazySynchronized = SingletionLazySynchronized.getSingletionLazySynchronized();
}
}.start();
}
}
}
测试结果
是否发生线程安全问题
测试结果可以看出 可以达到单例的效果, 同时线程安全, 然而效率太低下
第三种 饿汉模式
饿汉模式 单例类
import java.util.concurrent.TimeUnit;
/**
* 单例模式之饿汉模式
* 什么是饿汉模式
* 饿汉模式是指 无论我们是否用到该类的实例 我们都会创建其实例 可能会造成资源的浪费, 但是不会出现线程安全问题
*/
public class SingletionHungry {
/**
* 私有构造保证了该类不会被外部类 实例化
*/
private SingletionHungry(){
/**
* 如果测试结果打印了多次, 表示构造方法被多次调用,
* 说明出现线程安全问题
*/
System.out.println("是否发生线程安全问题");
}
/**
* 直接将该属性进行实例化操作
*/
private static SingletionHungry singletionHungry = new SingletionHungry();
public static SingletionHungry getSingletionHungry(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return singletionHungry;
}
}
饿汉模式测试类
import singletion.SingletionHungry;
public class SingletionHungryTest {
public static void main(String[] args) {
/**
* 创建100个线程,查看实例化的结果
*/
for(int i = 0; i<100; i++){
new Thread() {
@Override
public void run() {
SingletionHungry singletionHungry = SingletionHungry.getSingletionHungry();
}
}.start();
}
}
}
测试结果
是否发生线程安全问题
所以说明饿汉模式是线程安全的, 不过会造成资源的浪费,因为我们不一定会用到该类的对象
第四种 单例模式之静态内部类(推荐)
单例类
/**
* ①②③④⑤⑥⑦⑧⑨⑩
* 单例模式之内部静态类
* 什么是静态内部类?
* 静态内部类和他的外部类我们可以当作两个类,静态内部类不存在外部类的引用,
* 静态内部类的实例化不依赖于外部类的实例化,同时当外部类加载到内存的时候,
* 内部类也不会被加载到内存, 一个类被加载的时机是 当且仅当该类的静态成员
* (静态代码块, 构造器, 静态变量, 静态方法)被调用时。
*/
public class SingletionInnerStatic {
/**
* ③ 这里是私有构造方法
*/
private SingletionInnerStatic(){
System.out.println("是否发生线程安全问题");
}
private static class InnerStaticClass{
/**
* ② 当执行到这里的时候会执行外部类的私有构造
* 由于父类委托的类加载机制存在,类不会被重复加载
* 所以当前静态内部类只会被加载一次, 同时
* static和final修饰的singletionInnerStatic 也只会
* 被初始化一次,从而达到单例的目的
*/
private static final SingletionInnerStatic singletionInnerStatic = new SingletionInnerStatic();
}
public static SingletionInnerStatic getSingletionInnerStatic(){
/**
* ① 当我们调用该方法时,调用了静态内部类的静态变量
* 此时 静态内部类开始加载进行初始化操作
*/
return InnerStaticClass.singletionInnerStatic;
}
}
测试类
import singletion.SingletionInnerStatic;
public class SingletionInnerStaticTest {
public static void main(String[] args) {
for(int i = 0; i < 100; i++){
new Thread(){
@Override
public void run() {
SingletionInnerStatic.getSingletionInnerStatic();
}
}.start();
}
}
}
测试结果
是否发生线程安全问题
测试结果 完全满足我们的需求, 而且是懒加载, 避免了资源的浪费
第五种 单例模式之双重校验锁
单例类
/**
* 单例模式 之双重校验锁, 要想理解实现原理需要清楚
* 懒汉模式是为何造成线程不安全的,而这就是我们要解
* 决的问题
*/
public class SingletionDoubleCheck {
private static SingletionDoubleCheck singletionDoubleCheck = null;
private SingletionDoubleCheck(){
System.out.println("是否发生线程安全问题");
}
public static SingletionDoubleCheck getSingletionDoubleCheck(){
/**
* synchronized(一般都是类的class对象)会获取对象锁,可以保证
* 同步代码块不会同时被多线程访问。
* 举个例子, 下班了你和同事去吃饭, 有人给你点好了 你就没必要
* 再去根别人挤了, 你累别人也累, 浪费时间,如果你们都没点上,
* 那只能等着, 什么时候你们有人点好了, 然后你瞅一眼 有饭了
* 不挤了, 吃饭。。。。
*/
if(singletionDoubleCheck == null){
synchronized(SingletionDoubleCheck.class){
if(singletionDoubleCheck == null){
singletionDoubleCheck = new SingletionDoubleCheck();
}
}
}
return singletionDoubleCheck;
}
}
测试类
package test;
import singletion.SingletionDoubleCheck;
public class SingletionDoubleCheckTest {
public static void main(String[] args) {
/**
* 创建100个线程,查看实例化的结果
*/
for(int i = 0; i<100; i++){
new Thread() {
@Override
public void run() {
SingletionDoubleCheck singletionDoubleCheck = SingletionDoubleCheck.getSingletionDoubleCheck();
}
}.start();
}
}
}
测试结果
是否发生线程安全问题
线程安全 实现懒加载机制
就写到这吧, 其实以上实现都没有绝对的单例, 多线程搞不定 就用反射, 反射搞不定就反序列化, 太多东西能够破坏他们了, 经不起推敲啊, 不过入门足够, 技术如此, 只能到此了
参考资料
http://cantellow.iteye.com/blog/838473
https://blog.csdn.net/chenchaofuck1/article/details/51702129