单例类只能存在一个实例,单例类必须自己创建自己唯一的实例,单例类必须能给其他的所有对象提供这一实例。
单例模式主要包括立即加载/饿汉模式、延迟加载/懒汉模式等。
单例模式和多线程在一起时,就会出现问题,比如多个线程可以创建多个单例实例,这样是违背单例模型的初衷的。
一、立即加载/饿汉模式
1.立即加载即在调用方法前,实例已经被创建了,下面看一个实现立即加载和多线程的单例设计模式。
MyObject.java
/*
* 单例模式-》饿汉模式,记载调用方法前,实例已经被创建
*/
public class MyObject {
private static MyObject myObject=new MyObject();
private MyObject() {}
public static MyObject getInstance(){
return myObject;
}
}
Run.java
public class Run {
static Runnable t1=new Runnable(){
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
};
public static void main(String[] args){
Thread thread1=new Thread(t1);
Thread thread2=new Thread(t1);
Thread thread3=new Thread(t1);
thread1.start();
thread2.start();
thread3.start();
}
}
结果如下:
可以看到hashCode是同一个数值,说明是同一个对象。
二、延迟加载/懒汉模式
延迟加载是在调用get()方法时实例才被创建,常见的实现方法就是在get()方法中进行new 实例化。
但是延迟加载的缺点就是在多线程的环境中,会取出多个实例的情况,这样与单例模式的初衷是相悖的。
MyObject1.java
/*
* 延迟加载即懒汉模式在和多线程结合的时候,当有多个线程是就会创建出3个对象,这样完全不符合单例模式
*/
public class MyObject1 {
private static MyObject1 object;
private MyObject1() {}
public static MyObject1 getInstace() throws InterruptedException{
if(object!=null){}
else{
Thread.sleep(2000);
object=new MyObject1();
}
return object;
}
}
Run1.java
public class Run1 {
static Runnable t1=new Runnable() {
@Override
public void run() {
try {
System.out.println(MyObject1.getInstace().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
public static void main(String[] args){
Thread thread1=new Thread(t1);
Thread thread2=new Thread(t1);
thread1.start();
thread2.start();
}
}
结果如下:
可以看出两个hashCode值,说明是两个对象,与单例模式不符。
2.解决延迟加载缺点的方法
(1)对getInstance()方法声明synchronized关键字 synchronized public static MyObject1 getInstace()
(2)使用同步代码块 synchronized(MyObject.class)
(3)针对某些重要的代码块进行单独的同步
第一二种方法确实能解决问题,但是效率太慢,第三种方法能解决效率问题,但是还是没法解决懒汉模式遇到多线程的问题。
最好的解决方案即用户DCL双检查锁机制,即在同步代码块之前检查一次,在同步代码块中再检查一次。
MyObject2.java
/*
* 使用DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式
*/
public class MyObject2 {
private static MyObject2 myObject2;
private MyObject2() {}
public static MyObject2 getInstance() throws InterruptedException{
if(myObject2!=null){}
else{
Thread.sleep(2000);
synchronized (MyObject2.class) {
if(myObject2==null){
myObject2=new MyObject2();
}
}
}
return myObject2;
}
}
Run.java
public class Run2 {
static Runnable t1=new Runnable() {
@Override
public void run() {
try {
System.out.println(MyObject2.getInstance().hashCode());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
public static void main(String[] args){
Thread thread1=new Thread(t1);
Thread thread2=new Thread(t1);
thread1.start();
thread2.start();
}
}
结果如下:
可以看出是一个对象。
三、其他方法实现单例模式
(1)使用静态内置类实现单例模式
MyObject3.java
/*
* 使用静态内置类实现单例多线程
*/
public class MyObject3 {
private static class MyObject3Hander{
private static MyObject3 myObject3=new MyObject3();
}
private MyObject3() {}
public static MyObject3 getInstance(){
return MyObject3Hander.myObject3;
}
}
(2)使用static代码块实现单例模式
MyObject4.java
/*
* 使用static静态代码块实现单例模式
*/
public class MyObject4 {
private static MyObject4 instance=null;
private MyObject4() {
// TODO Auto-generated constructor stub
}
static{
instance=new MyObject4();
}
public static MyObject4 getInstance(){
return instance;
}
}
(3)使用enum数据雷翔实现单例模式