原文链接https://blog.csdn.net/qq_35860138/article/details/86477538
一. 什么是单例模式
因进程需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计。
二. 单例模式的特点
1、单例模式只能有一个实例。
2、单例类必须创建自己的唯一实例。
3、单例类必须向其他对象提供这一实例。
三. 单例模式VS静态类
在知道了什么是单例模式后,我想你一定会想到静态类,“既然只使用一个对象,为何不干脆使用静态类?”,这里我会将单例模式和静态类进行一个比较。
1、单例可以继承和被继承,方法可以被override,而静态方法不可以。
2、静态方法中产生的对象会在执行后被释放,进而被GC清理,不会一直存在于内存中。
3、静态类会在第一次运行时初始化,单例模式可以有其他的选择,即可以延迟加载。
4、基于2, 3条,由于单例对象往往存在于DAO层(例如sessionFactory),如果反复的初始化和释放,则会占用很多资源,而使用单例模式将其常驻于内存可以更加节约资源。
5、静态方法有更高的访问效率。
6、单例模式很容易被测试。
几个关于静态类的误解:
误解一:静态方法常驻内存而实例方法不是。
实际上,特殊编写的实例方法可以常驻内存,而静态方法需要不断初始化和释放。
误解二:静态方法在堆(heap)上,实例方法在栈(stack)上。
实际上,都是加载到特殊的不可写的代码内存区域中。
静态类和单例模式情景的选择:
情景一:不需要维持任何状态,仅仅用于全局访问,此时更适合使用静态类。
情景二:需要维持一些特定的状态,此时更适合使用单例模式。
四. 单例模式的实现
import java.util.concurrent.atomic.AtomicReference;
/**
*
- 类型:
- 懒汉式单例:使用时创建。考虑线程安全、优化加锁方式
- 饿汉式单例:类加载时创建。考虑对象大小
- @author Administrator
*/
//懒汉模式(线程不安全)
class Singleton1{
private Singleton1(){
System.out.println("产生01实例");
}
private static Singleton1 instance;
public static Singleton1 getInstance(){
if(instance==null){
instance=new Singleton1();
}
return instance;
}
}
//懒汉模式加锁(线程安全)大对象,少用,延迟加载
class Singleton2{
private Singleton2(){
System.out.println("产生02实例");
}
/**
* volatile 属性修改,立即可见。不能保证原子性。
*/
private static volatile Singleton2 instance;
public static synchronized Singleton2 getInstance(){
if(instance==null){
if(instance==null){
instance=new Singleton2();
}
}
return instance;
}
}
//饿汉模式(线程安全),小对象,频繁用。
//直接在运行这个类的时候进行一次loading,之后直接访问。显然,这种方法没有起到lazy loading的效果,考虑到前面提到的和静态类的对比,这种方法只比静态类多了一个内存常驻而已。
class Singleton3{
private Singleton3(){
System.out.println("产生03实例");
}
private static Singleton3 instance=new Singleton3();
public static Singleton3 getInstance(){
return instance;
}
}
//静态类内部加载(线程安全/懒汉)
//使用内部类的好处是,静态内部类不会在单例加载时就加载,而是在调用getInstance()方法时才进行加载,达到了类似懒汉模式的效果,而这种方法又是线程安全的。
class Singleton4{//
private Singleton4(){
System.out.println("产生04实例");
}
/**
* 内部类 延迟加载
*/
static class Inner{
private static Singleton4 instance = new Singleton4();
}
public static Singleton4 getInstance(){
return Inner.instance;
}
}
/**
- enum枚举类
- 通常用于定义一些固定的常量值
- 例如:enum gender{
- MALE,FEMALE
- };
- 这两个值通常会认为是2个对象实例
(1)自由串行化。(2)保证只有一个实例。(3)线程安全。
枚举单例这种方法问世以来,许多分析文章都称它是实现单例的最完美方法——写法超级简单,而且又能解决大部分的问题。避免反射攻击,避免序列化问题
不过我个人认为这种方法虽然很优秀,但是它仍然不是完美的——比如,在需要继承的场景,它就不适用了
*/
enum Singleton5{
instance;//单例对象(饿汉,线程安全,类加载时创建,且只创建一次
//构造方法默认无参且私有
private Singleton5(){
System.out.println("产生05实例");
}
public static Singleton5 getInstance() {
return instance;
}
public void otherMethods(){
System.out.println("其他方法");
}
}
//双重校验锁法(懒汉,通常线程安全,低概率不安全
/**
- 接下来我解释一下在并发时,双重校验锁法会有怎样的情景:
STEP 1. 线程A访问getInstance()方法,因为单例还没有实例化,所以进入了锁定块。
STEP 2. 线程B访问getInstance()方法,因为单例还没有实例化,得以访问接下来代码块,而接下来代码块已经被线程1锁定。
STEP 3. 线程A进入下一判断,因为单例还没有实例化,所以进行单例实例化,成功实例化后退出代码块,解除锁定。
STEP 4. 线程B进入接下来代码块,锁定线程,进入下一判断,因为已经实例化,退出代码块,解除锁定。??
STEP 5. 线程A获取到了单例实例并返回,线程B没有获取到单例并返回Null。
理论上双重校验锁法是线程安全的,并且,这种方法实现了lazyloading。
*/
class Singleton6{//大对象,少用,延迟加载
private Singleton6(){
System.out.println("产生06实例");
}
/**
* volatile 属性修改,立即可见。不能保证原子性。
*/
private static Singleton6 instance;
public static Singleton6 getInstance(){
if(instance==null){
synchronized (Singleton6.class) {
if(instance==null){
instance=new Singleton6();
}
}
}
return instance;
}
}
//双重校验锁法volatile版(懒汉,线程安全
class Singleton7{
private Singleton7(){
System.out.println("产生07实例");
}
/**
* volatile 属性修改,立即可见。不能保证原子性。在它的赋值完成之前,就不用会调用读操作
*/
private static volatile Singleton7 instance;
public static Singleton7 getInstance(){
if(instance==null){
synchronized (Singleton7.class) {
if(instance==null){//在这等待写操作完成,后读数据判断
instance=new Singleton7();
}
}
}
return instance;
}
}
/**
- ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
- 对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,
- 而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,
- 让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
*/
class Singleton8{
private Singleton8(){
System.out.println("产生单例08");
}
/**
*能够将某个对象绑定到当前线程,通过此类可以实现线程内部单例
*常用方法set绑定
*get获取绑定
*renmove移除绑定
*/
private static ThreadLocal<Singleton8> threadLocal = new ThreadLocal<>();
public static Singleton8 getInstance(){
Singleton8 looper = threadLocal.get();
if(looper==null){
looper = new Singleton8();
threadLocal.set(looper);
}
return looper;
}
public void remove(){
threadLocal.remove();
}
}
class Singleton9{
private Singleton9(){
System.out.println("产生单例09");
}
private static AtomicReference<Singleton9> instance = new AtomicReference<Singleton9>();
public static Singleton9 getInstance(){
for(;;) {
Singleton9 current = instance.get();
if(current!=null) {
return current;
}
current=new Singleton9();
if (instance.compareAndSet(null, current)) {
return current;
}
}
}
}
//--------------------------------------
public class TestSingleton01 {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
new Thread(){
@Override
public void run() {
doMethod09();
//Singleton5.instance.otherMethods();
}
}.start();
}
}
//-------------------------------------------
```java
private static void doMethod01() {
Singleton1 t1 = Singleton1.getInstance();
Singleton1 t2 = Singleton1.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod02() {
Singleton2 t1 = Singleton2.getInstance();
Singleton2 t2 = Singleton2.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod03() {
Singleton3 t1 = Singleton3.getInstance();
Singleton3 t2 = Singleton3.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod04() {
Singleton4 t1 = Singleton4.getInstance();
Singleton4 t2 = Singleton4.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod05() {
Singleton5 t1 = Singleton5.getInstance();
Singleton5 t2 = Singleton5.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod06() {
Singleton6 t1 = Singleton6.getInstance();
Singleton6 t2 = Singleton6.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod07() {
Singleton7 t1 = Singleton7.getInstance();
Singleton7 t2 = Singleton7.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
private static void doMethod08() {
Singleton08 l2 = Singleton08.getInstance();
System.out.println("l2"+l2);
Singleton08 l1 = Singleton08.getInstance();
System.out.println("l1"+l1);
l1.remove();
l2.remove();
}
private static void doMethod09() {
Singleton9 t1 = Singleton9.getInstance();
Singleton9 t2 = Singleton9.getInstance();
System.out.println(t1);
System.out.println(t2);
boolean isSingleton = t1.equals(t2);
System.out.println(isSingleton);
}
}