一、写这些原因是因为学习时候,我们只有知识的输入但是没有知识的转化和输出。所以想通过博客的形式来将学到知识 自己通过自己的语言和理解将其表述出来那么知识才能记得牢固才能 更能加深自己的理解。
1、单例模式(Singleton): 是一种常用的设计模式,在java应用中,单例对象能保证在一个jvm中,该对象的实例只有一个。
那么使用单例模式有什么好处呢?
a.某些类创建比较频繁,对于一些大型的对象创建时 比较消耗系统的资源。
b. 不需要new操作符去创建实例,减少了系统内存的使用频率,减轻GC压力。
c. 在整个系统中使用全局变量共享资源。
2、单例模式有分为 饿汉式,懒汉式,以及多线程版本。
一般简单不考虑多线程的话就是饿汉式,懒汉式创建。饿汉式就是立即创建一个对象而不是在需要的时候再创建。懒汉式就是在需要创建对象实例的时候才去创建。
3、首先使用 饿汉式来创建单例。
package com.springmvc.headfrist.singleton;
//最简单单例
/**
* <br/>
* @return
* @exception
* @author lin
* @Date 2017/5/3
**/
public class Singleton {
/**
持有私有静态实例,防止被引用
如果不设计为private ,而是public那么 我们在外边就可以通过类名来调用,并且可以将其设置为null.
*/
private static Singleton instance = new Singleton();
// 也可以使用静态块的方式创建
// static {
// instance = new Singleton();
// }
/**
* 私有构造方法,防止被实例化,如果在类里面有私有构造器,则下面
* 例如:Singleton singl=new Singleton();(为错误的)
*/
private Singleton() {
}
/**
* 创建静态方法,那么在外部调用时就可以通过类名.getInstance() 获取实例。
*/
public static Singleton getInstance() {
// 如果返回 return new Singleton(); 这样是不对的,因为这样每次调用都会产生一个新的实例。这和我们的单例模式 只有
// 实例相违背。
return instance;
}
}
/**
测试调用
*/
public class Demo {
public static void main(String[] args) {
//
SingletonTest instance = SingletonTest.getInstance();
int count =100;
for (int i = 0; i < count; i++) {
SingletonTest singletonTest = SingletonTest.getInstance();
// 比较地址,如果不相等那么就会执行下面的打印
if( instance != singletonTest){
System.out.println("一个新的对象被创建了"+ instance);
}
}
System.out.println("程序结束了");
}
}
运行结果是: 程序结束了
这就是说明了创建的 对象实例时同一个对象实例。
对于饿汉式模式的单例 有一个问题就是 Singleton对象暂时不需要使用 但是在类进行加载已经实例化好了这样就造成了一定程度上的性能损耗,所以 我们采用懒汉式的方式来创建单例模式。
4、懒汉式的方法来创建 就是在需要使用到实例对象是才去创建。
package com.springmvc.headfrist.singleton;
/**
* <br/>
* @author lin
* @Date 2017/5/3
**/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2 () {
System.out.println("---------------新的SingletonTest2对象被创建了--------------");
}
public static Singleton2 getInstance() {
if(instance == null){
instance = new Singleton2 ();
}
return instance ;
}
}
/**
测试
* @ClassName: Demo2
* @Description:
* @Author: lin
* @Date: 2017/5/3
* History:
* @<version> 1.0
*/
public class Demo2 {
public static void main(String[] args) {
int count =100;
for (int i = 0; i < count; i++) {
SingletonTest2.getInstance();
}
}
}
程序运行结果是: ---------------新的SingletonTest2对象被创建了--------------
从结果我们可以看到只是创建了一个实例对象。
这种创建就是懒汉式。和饿汉式的区别就是在声明instance没有直接去实例化,并且对instance进行一个null的判断。
不过对于上述的两种创建方式在多线程情况下就会出现问题。出现什么问题呢?我们使用懒汉式来测试一下
public class Demo2 {
public static void main(String[] args) {
int count =100;
ExecutorService executorService = new ThreadPoolExecutor(5,20,200,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5),
new DefaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < count; i++) {
executorService.execute(new MyThread());
}
}
}
class MyThread implements Runnable {
@Override
public void run() {
SingletonTest2.getInstance();
}
}
执行结果,可以看到在多线程下 会产生多个对象。所以在多线程下面这种方法不是单例模式。我们需要怎么去修改呢?让其在多线程下也只创建一个对象实例。一般我们想到的就是添加锁的方法来确保对象实例的创建。
5、我们可以通过 添加synchronized锁来实现多线程条件下的控制。
/**
* @ClassName: SingletonTest3
* @Description:
* @Author: lin
* @Date: 2017/5/3
* History:
* @<version> 1.0
*/
public class SingletonTest3 {
private static SingletonTest3 instance = null;
private SingletonTest3(){
System.out.println("---------------新的SingletonTest3对象被创建了--------------");
}
public static SingletonTest3 getInstance(){
if(instance == null){
synchronized (SingletonTest.class){
if(instance == null){
instance = new SingletonTest3();
}
}
}
return instance;
}
}
package com.springmvc.headfrist.singlet;
import com.springmvc.headfrist.DefaultThreadFactory;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
测试
* @ClassName: Demo3
* @Description:
* @Author: lin
* @Date: 2017/5/5
* History:
* @<version> 1.0
*/
public class Demo3 {
public static void main(String[] args) {
int count =100;
ExecutorService executorService = new ThreadPoolExecutor(5,20,200,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100),
new DefaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < count; i++) {
executorService.execute(new MyThread1());
}
}
}
class MyThread1 implements Runnable {
@Override
public void run() {
SingletonTest3.getInstance();
}
}
执行结果我们可以看到这种方式下 创建的实例对象只有一个,这样在多线程的情况下只生成一个实例对象
但是这种 双重检查模式 (目前被称为反模式,不提倡使用这种方式),进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。 synchronized 关键字修饰的代码块被称为同步块,synchronized关键字所引导的代码块就是临界区(锁的持有线程在其获取锁之后和释放之前这段时间所执行的代码被称为临界区 ),临界区内的代码会进行重排序 由于 instance = new SingletonTest3(); 对象的创建在JVM中可能会进行重排序,并且临界区外的代码 可以被重排序到临界区,但是临界区内的语句不能被重排序到临界区外,所以这种在多线程情况下存在风险。 不过我们可以使用volatile修饰instance 实例变量有效,解决该问题。
6、我们可以使用静态类内部类的方式来实现单例模式
package com.springmvc.headfrist.singlet;
/**
* 静态类部类
* @ClassName: SingletonTest4
* @Description:
* @Author: lin
* @Date: 2017/5/3
* History:
* @<version> 1.0
*/
public class SingletonTest4 {
private SingletonTest4(){
System.out.println("---------------新的SingletonTest4对象被创建了--------------");
}
private static class SingletonFactory{
final static SingletonTest4 instance = new SingletonTest4();
}
public static SingletonTest4 getInstance(){
return SingletonFactory.instance;
}
public void test(){
System.out.println("static class SingletonFactory ....");
}
}
package com.springmvc.headfrist.singlet;
import com.springmvc.headfrist.DefaultThreadFactory;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
测试
* @ClassName: Demo4
* @Description:
* @Author: lin
* @Date: 2017/5/3
* History:
* @<version> 1.0
*/
public class Demo4 {
public static void main(String[] args) {
int count =100;
ExecutorService executorService = new ThreadPoolExecutor(5,20,200,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100),
new DefaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < count; i++) {
executorService.execute(new MyThread2());
}
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
SingletonTest4.getInstance();
}
}
执行结果
从执行的结果来看 产生的实例对象只有一个,一般来说创建单例的模式有很多种没有十分完美方案。我们只能根据实际情况,选择选择适合自己应用场景的方法。
7、注意序列化对单例的破坏
package com.springmvc.headfrist.singleton;
import java.io.Serializable;
/**
* 使用双重校验锁方式实现单例
* 测试 序列化对一个单例的破坏
* <p>Title:SingletonSerializable</p>
* @author liuwanlin
* @date 2017年5月4日上午11:45:02
*/
public class SingletonSerializable implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8607398639911248517L;
private volatile static SingletonSerializable singletonSerializable;
private SingletonSerializable(){
System.out.println("---------------新的SingletonSerializable对象被创建了--------------");
}
public static SingletonSerializable getSingletonSerializable(){
if(singletonSerializable==null){
synchronized (SingletonSerializable.class) {
if(singletonSerializable == null){
singletonSerializable = new SingletonSerializable();
}
}
}
return singletonSerializable;
}
// private Object readResolve() {
// return singletonSerializable;
// }
}
public class Demo4Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 输出流 字节
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("tempFile"));
// 调用writeObject的方法将字节写到流中
obj.writeObject(SingletonSerializable.getSingletonSerializable());
File file = new File("tempFile");
// 输入流
ObjectInputStream outputStream= new ObjectInputStream(new FileInputStream(file));
SingletonSerializable newInstance = (SingletonSerializable ) outputStream.readObject();
System.out.println( newInstance == SingletonSerializable.getSingletonSerializable());
obj.flush();
obj.close();
outputStream.close();
}
}
测试结果如下
从结果可以知道 序列化之后两个实例对象不相等,这不符合单例模式的定义。那么是哪里出了问题呢?
我们进行debug进一步分析 ,下面读取流地方 我们去看看到底做了些什么
进入ObjectInputStream类中 readObject()方法。首先会去判断enableOverride是否为true, 如果为true就执行readObjectOverride(), 如果没有那么就往下执行。这为false所以继续往下执行。执行到readObject0()方法的时候这里面做了什么处理呢?
进入到readObject0方法
/**
* Underlying readObject implementation.
*/
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
在这个方法中实际上调用的是 readOrdinaryObject(unshared);方法
在该方法中有个三目运算 obj = desc.isInstantiable()? desc.newInstance(): null;
而isInstantiable()方法的 表示 如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true. 对于desc.newInstance() 则表示 该方法通过反射的方式调用无参构造方法新建一个对象。
这里使用序列化的方式所以 desc.isInstantiable() 为true 那么 将使用 反射的方式去创建一个新的对象。所以从这里我们就可以知道 为什么序列化可以破坏单例了。
那么我们该如果去防止序列化对单例的破坏呢?我们可以在SingletonSerializable定义一个readResolve方法。我们将上面SingletonSerializable类中 readResolve方法注释取消再进行测试。
public class SingletonSerializable implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8607398639911248517L;
private volatile static SingletonSerializable singletonSerializable;
private SingletonSerializable(){
System.out.println("---------------新的SingletonSerializable对象被创建了--------------");
}
public static SingletonSerializable getSingletonSerializable(){
if(singletonSerializable==null){
synchronized (SingletonSerializable.class) {
if(singletonSerializable == null){
singletonSerializable = new SingletonSerializable();
}
}
}
return singletonSerializable;
}
private Object readResolve() {
return singletonSerializable;
}
}
public class Demo4Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 输出流 字节
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("tempFile"));
// 调用writeObject的方法将字节写到流中
obj.writeObject(SingletonSerializable.getSingletonSerializable());
File file = new File("tempFile");
// 输入流
ObjectInputStream outputStream= new ObjectInputStream(new FileInputStream(file));
SingletonSerializable newInstance = (SingletonSerializable ) outputStream.readObject();
System.out.println( newInstance == SingletonSerializable.getSingletonSerializable());
obj.flush();
obj.close();
outputStream.close();
}
}
测试结果
从测试结果来看 两个对象实例相同,说明是同一个对象。那么为什么添加了readResolve方法 就能避免序列化对象单例模式的破坏呢?在readOrdinaryObject 方法中 一个判断是调用 desc.hasReadResolveMethod()方法 。 hasReadResolveMethod方法是 如果一个类 实现了serializable 或者 externalizable接口的类中包含readResolve则返回true。 那么它将会去调用 invokeReadResolve方法 ,通过反射的方式调用要被反序列化的类的readResolve方法。
所以,原理也就清楚了,主要在SingletonSerializable 中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
hasReadResolveMethod方法。
invokeReadResolve 方法