singleton pattern
单例模式有以下特点: 1、单例类只能有一个实例。 2、单例类必须自己创建自己的唯一实例。 3、单例类必须给所有其他对象提供这一实例。 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
饿汉单例模式
饿汉模式是在类中直接进行静态的初始化,类一加载便创建类的实例化对象。这种模式是线程安全的,并且没有延迟加载。
1、静态变量:
public class Ehan(){
//私有化构造器方法
private Ehan(){}
//设置静态变量
private static final Ehan ehan=new Ehan();
//提供对外获取唯一实例的方法
public static Ehan getInstance(){
return ehan;
}
}
优点:这种写法比较简单,在类装载的时候就完成实例化,避免了线程同步问题;
缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,就会造成内存浪费;
这种方式是基于classloader机制避免了多线程的同步问题,不过,instance在类装载时就实例化;
2、静态代码块:
public class Ehan2(){
private Ehan2(){}
private static Ehan2 ehan2;
static{
ehan2=new Ehan2();
}
public static Ehan2 getInstance(){
return ehan2;
}
}
懒汉单例模式
懒汉模式是在类中进行静态初始化但赋了null值,但在类中创建对应的静态工厂方法进行对象的实例化,只有当有线程调用了这个类才进行对象的实例化,这种模式是线程不安全的。
public class LanHan(){
private LanHan(){}
private static LanHan lanHan=null;
public static LanHan getInstance(){
if(lanHan==null){
lanHan=new LanHan();
}
return lanHan;
}
}
有三种常见的方法来实现懒汉模式的线程安全。 1、直接在静态工厂方法上加上synchronized同步方法,但这种方法调用都需要进行同步,严重影响了性能。
public class LanHan(){
private LanHan(){}
private static LanHan lanHan=null;
public static synchronized LanHan getInstance(){
if(lanHan==null){
lanHan=new LanHan();
}
return lanHan;
}
}
这种方式效率低,synchronized的范围太大;
2、双重检查锁定。通过双重的if判断保障只有一个线程进行初始化操作,避免了每次都进行同步,大大提升了性能,但双重if在一定程度上也会影响性能。
public class LanHan(){
private LanHan(){}
private static LanHan lanHan=null;
public static LanHan getInstance(){
if(lanHan==null){
syschronized(LanHan.class){
if(lanHan==null){
lanHan=new LanHan();
}
}
}
return lanHan;
}
}
双重判断避免了创建多个实例,也实现了懒加载;
3、通过定义静态内部类并在类中进行静态初始化,利用classloader的机制只有一个线程能进入到初始化的程序,实现了线程安全,是三种方法里面性能最高的方法。
public class LanHan(){
private static class Instance(){
private static final LanHan lanHan=new LanHan();
}
public static LanHan getInstance(){
return Instance.lanHan;
}
}
静态内部类方式在类被加载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载静态内部类,从而实例类的实例化;
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的;
实例测试
public class DanLi {
private String name;
private DanLi(){}
private static DanLi danLi=null;
public static DanLi getInstance(){
if(danLi==null){
synchronized(DanLi.class){
if(danLi==null){
danLi=new DanLi();
}
}
}
return danLi;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printResult(){
System.out.println(name+"输出");
}
}
测试用例:
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DanLi dl1=DanLi.getInstance();
dl1.setName("001");
DanLi dl2=DanLi.getInstance();
dl2.setName("007");
dl1.printResult();
dl2.printResult();
if(dl1==dl2){
System.out.println("true");
}else{
System.out.println("false");
}
}
}
测试懒汉模式的线程不安全性
public class DanLi{
private long name;
private DanLi(long name){
this.name =name;
}
private static DanLi danLi=null;
public static DanLi getInstance(long l){
if(danLi==null){
danLi=new DanLi(l);
}
return danLi;
}
public long getName() {
return name;
}
public void setName(long name) {
this.name = name;
}
public void printResult(){
System.out.println(name+"输出");
}
}
线程类:
public class DanLiThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
DanLi danLi=DanLi.getInstance(System.currentTimeMillis());
System.out.println(danLi.getName());
}
}
测试类:
public class Test1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DanLiThread dlt=new DanLiThread();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
new Thread(dlt).start();
}
}
运行结果:
枚举类实现单例
枚举和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,可以利用这一特性实现单例;使用枚举实现单例的优点包括:功能完整、使用简洁、无偿地自动提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化(对于反射攻击enum类会抛出异常,反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败;对于序列化enum能自动解决)
实体类:
public class DataEnum {
}
枚举类:
public enum EnumTest {
DATASOURCE;
private DataEnum de;
private EnumTest() {
de=new DataEnum();
}
public DataEnum getInstance(){
return de;
}
}
测试类:
public class DanliTest {
public static void main(String[] args) {
DataEnum de1=EnumTest.DATASOURCE.getInstance();
DataEnum de2=EnumTest.DATASOURCE.getInstance();
System.out.println(de1==de2);
}
}
运行结果:
单例模式的序列化与反序列化实现
我们知道java对象的序列化操作是实现serializable接口,我们就可以把它往内存中写再从内存里读出从而“组装”成一个与原来一模一样的对象,但是当我们遇到单例序列化的时候,反序列化会创建新的对象,这就打破了单例的规则;
通过更深入的了解我们知道serializable and Externalizable classes,方法readResolve允许class在反序列化返回对象前替换、解析在流中读出来的对象;
实现readResolve方法,一个class可以直接控制反序列化返回的类型和对象引用,方法readResolve会在ObjectInputStream已经读取一个对象并在准备返回前调用,ObjectOutputStream会检查对象的class是否定义了readResolve方法,如果定义了,将由readResolve方法指定返回的对象,返回对象的类型一定要是兼容的,否则会抛出ClassCastException;
实现类:
public class MyObject implements Serializable {
// 内部类方式
private static class MyObjectHandler {
private static final MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return MyObjectHandler.myObject;
}
}
测试类:
public class SerTest {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File(
"D:/myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fisRef = new FileInputStream(new File(
"D:/myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
模拟打印机工作原理
原理:当有打印工作进来,就把单例对象锁上,等打印工作结束之后再释放掉锁让其他线程获取单例对象进行打印,我们后面将对比多例模式。
单例模式
实体类:
public class DanLi{
private String name;
private DanLi(){
}
private static DanLi danLi=null;
public static DanLi getInstance(){
if(danLi==null){
synchronized(DanLi.class){
if(danLi==null){
danLi=new DanLi();
}
}
}
return danLi;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printResult(){
System.out.println(name+"输出");
}
}
线程类:
public class DanLiThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
DanLi danLi=DanLi.getInstance();
try {
synchronized(danLi){
Thread.sleep(5000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
danLi.setName("001");
System.out.println(danLi.getName());
System.out.println(new Date());
}
}
测试类:
public class Test1 {
/**
* @param args
*/
public static void main(String[] args) {
DanLiThread dlt=new DanLiThread();
new Thread(dlt).start();
new Thread(dlt).start();
}
}
运行结果:
多例模式
实体类:
public class DanLi{
private String name;
private DanLi(){
}
private static DanLi danLi=null;
public static DanLi getInstance(){
danLi=new DanLi();
return danLi;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printResult(){
System.out.println(name+"输出");
}
}
线程类:
public class DanLiThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
DanLi danLi=DanLi.getInstance();
try {
synchronized(danLi){
Thread.sleep(5000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
danLi.setName("001");
System.out.println(danLi.getName());
System.out.println(new Date());
}
}
测试类:
public class Test1 {
/**
* @param args
*/
public static void main(String[] args) {
DanLiThread dlt=new DanLiThread();
new Thread(dlt).start();
new Thread(dlt).start();
}
}
运行结果: