设计模式之单例模式
概念:单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。
饿汉式单例:
package com.example.SingDemo;
/**
* description
* <p>
* 饿汉式单例
* 饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码:
* @author slliao
* @date 2019/5/5 14:12.
*/
public class HungryMySingleton {
private static HungryMySingleton instance = new HungryMySingleton();
private HungryMySingleton(){
}
public static HungryMySingleton getInstance(){
return instance;
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:15.
*/
public class HungryMyThread extends Thread {
@Override
public void run() {
System.out.println(HungryMySingleton.getInstance().hashCode());
}
public static void main(String[] args) {
HungryMyThread[] mts = new HungryMyThread[10];
int len=mts.length;
for (int i=0;i<len;i++){
mts[i]=new HungryMyThread();
}
for ( int j=0;j<len;j++){
mts[j].start();
}
}
}
结果为从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。
缺点:饿汉模式天生是线程安全的,使用时没有延迟
优点:启动时即创建实例,启动慢,有可能造成资源浪费
懒汉式单例
package com.example.SingDemo;
/**
* description
* <p>
* 懒汉式单例
* 懒汉式单例是指在方法调用获取实例时才创建实例,相对于饿汉式显得“不急迫”,所以被叫做“懒汉模式”
* @author slliao
* @date 2019/5/5 14:38.
*/
public class FullMySingleton {
private static FullMySingleton instance =null;
public static FullMySingleton getInstance(){
try {
if (instance == null){
//新建实例对象之前做一些准备工作,停顿一段时间
Thread.sleep(200);
instance = new FullMySingleton();
}
}catch (InterruptedException e){
e.printStackTrace();
}
return instance;
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:44.
*/
public class FullMyThread extends Thread {
@Override
public void run(){
System.out.println(FullMySingleton.getInstance().hashCode());
}
public static void main(String[] args) {
FullMyThread[] mts = new FullMyThread[10];
int length = mts.length;
for (int i=0;i<length;i++){
mts[i] = new FullMyThread();
}
for (int j=0;j<length;j++){
mts[j].start();
}
}
}
结果为
从运行结果来看,这种懒汉式单例模式无法保证实例唯一。
继续改进,方法中声明synchronized
package com.example.SingDemo;
/**
* description
* <p>
* 同步方法块
* 方法中声明synchronized关键字
* @author slliao
* @date 2019/5/5 14:55.
*/
public class FullMySingletonMethod {
private static FullMySingletonMethod instance =null;
//对方法加锁
public synchronized static FullMySingletonMethod getInstance(){
try {
if (instance == null){
//新建实例对象之前做一些准备工作,停顿一段时间
Thread.sleep(200);
instance = new FullMySingletonMethod();
}
}catch (InterruptedException e){
e.printStackTrace();
}
return instance;
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:44.
*/
public class FullMyThreadMethod extends Thread {
@Override
public void run(){
System.out.println(FullMySingletonMethod.getInstance().hashCode());
}
public static void main(String[] args) {
FullMyThreadMethod[] mts = new FullMyThreadMethod[10];
int length = mts.length;
for (int i=0;i<length;i++){
mts[i] = new FullMyThreadMethod();
}
for (int j=0;j<length;j++){
mts[j].start();
}
}
}
结果为
从结果中可以看到,保证了实例对象的唯一性 。但是,对整个方法进行加锁后,会发现执行效率变低了(加锁的范围越大,效率越低)。
继续改进,对关键代码块进行加锁
package com.example.SingDemo;
/**
* description
* <p>
* 同步代码块
* 代码中声明synchronized关键字
* @author slliao
* @date 2019/5/5 14:55.
*/
public class FullMySingletonCode {
private static FullMySingletonCode instance =null;
public static FullMySingletonCode getInstance(){
try {
//对部分代码块加锁
synchronized (FullMySingletonCode.class){
if (instance == null){
//新建实例对象之前做一些准备工作,停顿一段时间
Thread.sleep(200);
instance = new FullMySingletonCode();
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
return instance;
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:44.
*/
public class FullMyThreadCode extends Thread {
@Override
public void run(){
System.out.println(FullMySingletonCode.getInstance().hashCode());
}
public static void main(String[] args) {
FullMyThreadCode[] mts = new FullMyThreadCode[10];
int length = mts.length;
for (int i=0;i<length;i++){
mts[i] = new FullMyThreadCode();
}
for (int j=0;j<length;j++){
mts[j].start();
}
}
}
结果为
保证了实例对象的唯一性 。
静态内置类实现单例模式
package com.example.SingDemo;
/**
* description
* <p>
*使用静态内部类来实现单例,感觉和饿汉式差不多,在初始化静态变量时只创建一次对象而已,之后使用的都是同一对象
* @author slliao
* @date 2019/5/5 14:12.
*/
public class InternalMySingleton {
private static class InternalMethod{
private static InternalMySingleton instance = new InternalMySingleton();
}
public static InternalMySingleton getInstance(){
return InternalMethod.instance;
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:15.
*/
public class InternalMyThread extends Thread {
@Override
public void run() {
System.out.println(InternalMySingleton.getInstance().hashCode());
}
public static void main(String[] args) {
InternalMyThread[] mts = new InternalMyThread[10];
int len=mts.length;
for (int i=0;i<len;i++){
mts[i]=new InternalMyThread();
}
for ( int j=0;j<len;j++){
mts[j].start();
}
}
}
结果
但是当使用静态内部类在遇到序列化对象时,默认的方式运行得到的结果就是多例的
package com.example.SingDemo;
import java.io.Serializable;
/**
* description
* <p>
*序列化与反序列化的单例模式实现
* @author slliao
* @date 2019/5/5 14:12.
*/
public class InternalMySingletonSerializable implements Serializable {
private static final long serialVersionUID = 1L;
private static class InternalMethod{
private static InternalMySingletonSerializable instance = new InternalMySingletonSerializable();
}
public static InternalMySingletonSerializable getInstance(){
return InternalMethod.instance;
}
}
测试
package com.example.SingDemo;
import java.io.*;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 15:39.
*/
public class InputAndOutForSingleton {
public static void main(String[] args) {
InternalMySingletonSerializable singleton = InternalMySingletonSerializable.getInstance();
File file = new File("SerializableSingleton.txt");
try {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
fos.close();
oos.close();
System.out.println(singleton.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
InternalMySingletonSerializable rSingleton = (InternalMySingletonSerializable) ois.readObject();
fis.close();
ois.close();
System.out.println(rSingleton.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
结果为:
从结果可以看出***对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。
解决办法就是在反序列化的过程中使用readResolve()方法。在类中加入readResolve方法。
package com.example.SingDemo;
import java.io.Serializable;
/**
* description
* <p>
*序列化与反序列化的单例模式实现
* @author slliao
* @date 2019/5/5 14:12.
*/
public class InternalMySingletonSerializable implements Serializable {
private static final long serialVersionUID = 1L;
private static class InternalMethod{
private static InternalMySingletonSerializable instance = new InternalMySingletonSerializable();
}
public static InternalMySingletonSerializable getInstance(){
return InternalMethod.instance;
}
/**
* 该方法在反序列化时会被调用,反序化时就会产生一个克隆的对象,这就打破了单例的规则
* 那就是如果被反序列化的对象的类存在readResolve这个方法,
* 他会调用这个方法,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。
* @return
*/
protected Object readResolve(){
System.out.println("调用了readResolve方法!");
return InternalMethod.instance;
}
}
结果为
使用双检查锁机制。
双重检查锁
package com.example.SingDemo;
/**
* description
* <p>
* 懒汉式单例
* 双检查锁机制
* @author slliao
* @date 2019/5/5 14:38.
*/
public class FullMySingletonDouble {
/**
* 使用volatile关键字保其可见性
*/
volatile private static FullMySingletonDouble instance =null;
public static FullMySingletonDouble getInstance(){
try {
if (instance == null){
//新建实例对象之前做一些准备工作,停顿一段时间
Thread.sleep(200);
synchronized (FullMySingletonDouble.class){
if (instance ==null){
instance = new FullMySingletonDouble();
}
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
return instance;
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:44.
*/
public class FullMyThreadDouble extends Thread {
@Override
public void run(){
System.out.println(FullMySingletonDouble.getInstance().hashCode());
}
public static void main(String[] args) {
FullMyThreadDouble[] mts = new FullMyThreadDouble[100];
int length = mts.length;
for (int i=0;i<length;i++){
mts[i] = new FullMyThreadDouble();
}
for (int j=0;j<length;j++){
mts[j].start();
}
}
}
结果为
声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,保证不被重复的实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性
懒汉式总结:Lazy load,用到才加载,非线程安全。如何保证线程安全呢:
(1) synchronized getInstance()。
(2)双重检查加锁(volatile)。
枚举实现单例模式
枚举单例模式
package com.example.SingDemo;
/**
* description
* <p>
* 使用枚举来实现单例
* @author slliao
* @date 2019/5/5 16:13.
*/
public class EnumSingleton {
public static EnumSingleton getInstance(){
return SingEnum.instance.getSingleton();
}
enum SingEnum {
instance;
private EnumSingleton singleton;
SingEnum(){
singleton = new EnumSingleton();
}
private EnumSingleton getSingleton(){
return singleton;
}
}
}
测试
package com.example.SingDemo;
/**
* description
* <p>
*
* @author slliao
* @date 2019/5/5 14:15.
*/
public class EnumMyThread extends Thread {
@Override
public void run() {
System.out.println(EnumSingleton.getInstance().hashCode());
}
public static void main(String[] args) {
EnumMyThread[] mts = new EnumMyThread[10];
int len=mts.length;
for (int i=0;i<len;i++){
mts[i]=new EnumMyThread();
}
for ( int j=0;j<len;j++){
mts[j].start();
}
}
}
结果
由于枚举实例的创建默认就是线程安全的,你不需要担心双检锁问题。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject等方法。
普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
详细介绍可以看看这个 https://www.cnblogs.com/z00377750/p/9177097.html