JUC并发编程 --单例模式
Posted kuzhongyk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程 --单例模式相关的知识,希望对你有一定的参考价值。
单例模式是java中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
使用场景:
要求产生唯一序列号。
WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等。
饿汉式单例模式
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
package com.ning.single;
/**
* @author 16790
* 饿汉式单例模式
*/
public class Hungry {
private Hungry(){}
private static Hungry hungry=new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
懒汉式单例模式
package com.ning.single;
/**
* @author 16790
* 懒汉式单例模式
不支持多线程,在多线程下,这种模式不安全
*/
public class LazyMan {
private LazyMan(){}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
DCL 懒汉式单例模式(DCL即双检锁 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
package com.ning.single;
/**
* @author 16790
* DCL懒汉式单例模式
*/
public class DCLLazyMan {
private DCLLazyMan(){}
//这里为什么要使用volatile?
/**
dclLazyMan=new DCLLazyMan();这个不是原子操作,要防止指令重排
为什么不是原子操作?
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
*/
private static volatile DCLLazyMan dclLazyMan;
public static DCLLazyMan getInstance(){
if (dclLazyMan==null){
//同步代码块
synchronized (DCLLazyMan.class){
dclLazyMan=new DCLLazyMan();
}
}
return dclLazyMan;
}
}
静态内部类
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
package com.ning.single;
/**
* @author 16790
* 静态内部类
*/
public class StaticClass {
private StaticClass(){}
public static StaticClass getInstance(){
return InnerClass.STATIC_CLASS;
}
//内部类
public static class InnerClass{
private static final StaticClass STATIC_CLASS=new StaticClass();
}
}
枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
package com.ning.single;
/**
* @author 16790
* 枚举
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
单例不安全,反射和序列化可以破坏单例
使用反射创建对象
package com.ning.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author 16790
* 懒汉式单例模式
*/
public class LazyMan {
private LazyMan(){}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
class test{
public static void main(String[] args) throws Exception {
LazyMan lazyMan=LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan lazyMan1 = constructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}
}
测试结果:
com.ning.single.LazyMan@1b6d3586
com.ning.single.LazyMan@4554617c
分析:从结果来看,创建了两个单例对象,这样就破坏了单例模式。破坏的原因是将构造方式的private的检查,通过constructor.setAccessible(true)进行了屏蔽。
解决办法:将构造方法的调用次数设置一个标志,来进行表示调用次数,超过一次之后,在调用构造方法直接抛出异常。
package com.ning.single;
import java.lang.reflect.Constructor;
/**
* @author 16790
* DCL懒汉式单例模式
*/
public class DCLLazyMan {
private static boolean flag=false;
private DCLLazyMan(){
synchronized (DCLLazyMan.class){
if (flag==false){
flag=true;
}else{
throw new RuntimeException("不能创建多个实例");
}
}
}
private static volatile DCLLazyMan dclLazyMan;
public static DCLLazyMan getInstance(){
if (dclLazyMan==null){
synchronized (DCLLazyMan.class){
dclLazyMan=new DCLLazyMan();
}
}
return dclLazyMan;
}
}
class Test{
public static void main(String[] args) throws Exception{
Constructor<DCLLazyMan> constructor = DCLLazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
DCLLazyMan dclLazyMan = constructor.newInstance();
DCLLazyMan dclLazyMan2= constructor.newInstance();
System.out.println(dclLazyMan);
System.out.println(dclLazyMan2);
}
}
结果:
序列化破坏单例模式
测试
package com.ning.single;
import java.io.*;
import java.lang.reflect.Constructor;
/**
* @author 16790
* DCL懒汉式单例模式
*/
public class SerializableTest implements Serializable {
private static boolean flag=false;
private SerializableTest(){
synchronized (SerializableTest.class){
if (flag==false){
flag=true;
}else{
throw new RuntimeException("不能创建多个实例");
}
}
}
private static volatile SerializableTest serializableTest;
public static SerializableTest getInstance(){
if (serializableTest==null){
synchronized (SerializableTest.class){
serializableTest=new SerializableTest();
}
}
return serializableTest;
}
}
class sTest{
public static void main(String[] args) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(SerializableTest.getInstance());
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("tempFile")));
SerializableTest serializableTest1 = (SerializableTest) ois.readObject();
//判断是否是同一个对象
System.out.println(serializableTest1);
System.out.println(SerializableTest.getInstance());
}
}
结果:
输出的结果不一样说明:
通过对该类的序列化与反序列化得到的一个对象是一个新对象。
为什么序列化会破坏单列模式?
这里重点看一下readOrdinaryObject方法的代码:
private Object readOrdinaryObject(boolean unshared)
throws IOException
//此处省略部分代码
Object obj;
try {
/**
isInstantiable():如果一个serializable/externalizable的类可以
在运行时被实例化,那么该方法就返回true。
desc.newInstance:该方法通过反射的方式调用无参构造方法新建
一个对象。
*/
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
//此处省略部分代码
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
所以也可以解释为什么序列化可以破坏单例了?
序列化会通过反射调用无参数的构造方法创建一个新的对象。
防止序列化破坏单例模式:
只要在该类中定义readResolve()方法就可以解决该问题:
package com.ning.single;
import java.io.*;
import java.lang.reflect.Constructor;
/**
* @author 16790
* DCL懒汉式单例模式
*/
public class SerializableTest implements Serializable {
private static boolean flag=false;
private SerializableTest(){
synchronized (SerializableTest.class){
if (flag==false){
flag=true;
}else{
throw new RuntimeException("不能创建多个实例");
}
}
}
private static volatile SerializableTest serializableTest;
public static SerializableTest getInstance(){
if (serializableTest==null){
synchronized (SerializableTest.class){
serializableTest=new SerializableTest();
}
}
return serializableTest;
}
public Object readResolve(){
return serializableTest;
}
}
class sTest{
public static void main(String[] args) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(SerializableTest.getInstance());
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("tempFile")));
SerializableTest serializableTest1 = (SerializableTest) ois.readObject();
//判断是否是同一个对象
System.out.println(serializableTest1);
System.out.println(SerializableTest.getInstance());
}
}
原理:
还是在readOrdinaryObject方法中
//此处省略部分代码
/**
hasReadResolveMethod():如果实现了serializable 或者 externalizable接口的
类中包含readResolve则返回true
invokeReadResolve():通过反射的方式调用要被反序列化的类的readResolve方法。
*/
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
以上是关于JUC并发编程 --单例模式的主要内容,如果未能解决你的问题,请参考以下文章