饿汉模式
package com.rainhey.single;
public class MyObject {
private static MyObject myObject=new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
}
在立即加载/饿汉模式中,在调用方法前,实例就已经被工厂创建了
饿汉式的缺点是不能有其他实例变量,因为getInstance方法没有同步,可能出现非线程安全问题
懒汉模式
懒汉模式实例是延迟加载的,延迟加载是指调用get方法时实例才被工厂创建,常见办法是在get方法中进行new实例化,延迟加载有“缓慢”的意味
单线程中的懒汉模式
package com.rainhey.single;
public class MyObject1 {
private static MyObject1 myObject1;
private MyObject1(){
}
public static MyObject1 getInstance(){
if(myObject1==null){
myObject1=myObject1;
}
return myObject1;
}
}
使用synchronized关键字或者同步代码块解决多线程的单例问题
package com.rainhey.single;
public class MyObject1 {
private static MyObject1 myObject1;
private MyObject1(){
}
/*synchronized public static MyObject1 getInstance(){
if(myObject1==null){
myObject1=myObject1;
}
return myObject1;
}*/
public static MyObject1 getInstance(){
synchronized (MyObject1.class){
if(myObject1==null){
myObject1=myObject1;
}
return myObject1;
}
}
}
虽然能解决多线程下的单例问题,但加入synchronized关键字或者同步代码块的方法运行效率非常低
DCL机制
双检查锁,使用DCL机制成功解决了懒汉模式遇到多线程的问题,也是大多数多线程结合单例模式使用的解决方案
package com.rainhey.single;
public class MyObject2 {
private volatile static MyObject2 myObject2;
private MyObject2(){
}
public static MyObject2 getInstance() {
if (myObject2 == null) {
/*这里可以模拟创建对象之前做一些准备性工作*/
synchronized (MyObject2.class) {
if (myObject2 == null) {
myObject2 = new MyObject2();
}
}
}
return myObject2;
}
}
- 在创建对象处才添加synchronized锁,同时在synchronized方法内部再次判断是否对象为空
- volatile修饰变量,增加变量在多个线程间的可见性
- volatile禁止
myObject=new MyObject()
代码重排,因为这行代码在内部分为3个步骤:1.分配对象的内存空间 2.初始化对象 3. 设置instance指向分配的内存地址,volatile保证了这三步的顺序性,防止访问myObject对象时还是数据类型的默认值
静态内部类实现单例模式
package com.rainhey.single;
public class MyObject3 {
private static class MyObjectHandler{
private static MyObject3 myObject=new MyObject3();
}
private MyObject3(){
}
public static MyObject3 getInstance(){
return MyObjectHandler.myObject;
}
}
序列化和反序列化中单例模式的实现
当将单例的对象进行序列化时,使用默认的反序列化行为取出的对象是多例的
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行
public class UserInfo {
}
public class TestObject implements Serializable {
private static final long serialVersionUID=888L;
public static UserInfo userInfo=new UserInfo();
private static TestObject myobject=new TestObject();
private TestObject(){
}
public static TestObject getInstance(){
return myobject;
}
/*protected Object readResolve() throws ObjectStreamException{
System.out.println("调用了readResolve方法");
return TestObject.myobject;
}*/
}
package com.rainhey.single;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SaveAndRead {
public static void main(String[] args) {
TestObject myobject = TestObject.getInstance();
try {
System.out.println("序列化 myobject="+myobject.hashCode()+" userinfo="+TestObject.userInfo.hashCode());
FileOutputStream fileOutputStream = new FileOutputStream("E:\\test.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(myobject);
fileOutputStream.close();
objectOutputStream.close();
}catch (Exception e){
e.printStackTrace();
}
try {
FileInputStream fileInputStream = new FileInputStream("E:\\test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
TestObject testObject = (TestObject) objectInputStream.readObject();
fileInputStream.close();
objectInputStream.close();
System.out.println("反序列化 myobject="+testObject.hashCode()+" userinfo="+TestObject.userInfo.hashCode());
}catch (Exception e){
e.printStackTrace();
}
}
}
可以发现序列化前和反序列化后两个myobject的hashcode不一样,说明在反序列化时创建了新的对象,内存中存在两个myobject对象,不是单例的,但是Userinfo对象却得到了复用,为了实现在序列化和反序列化都是单例的效果,可以在反序列化时使用readResolve方法,对原有的对象进行复用,添加代码如上图注释部分
使用static代码块实现单例模式
package com.rainhey.single1;
public class MyObject1 {
private static MyObject1 myObject1;
private MyObject1(){
}
static {
myObject1=new MyObject1();
}
public static MyObject1 getInstance(){
return myObject1;
}
}
使用enum实现单例模式
enum枚举数据类型的特征和静态代码块的特征相似,在使用枚举类时,构造方法会被自动调用,可以应用这个特征实现单例模式
example1:
package com.rainhey.single1;
public enum EnumTest{
INSTANCE;
}
class Test{
public static void main(String[] args) {
EnumTest instance1 = EnumTest.INSTANCE;
EnumTest instance2 = EnumTest.INSTANCE;
System.out.println(instance1==instance2);
}
}
example2:
package com.rainhey.single1;
import java.sql.Connection;
import java.sql.DriverManager;
public enum MyObject {
ConnectionsFactory;
private Connection connection;
private MyObject(){
try {
System.out.println("调用MyObject的构造方法");
String url="";
String username="";
String password="";
String dirverName="";
Connection connection = DriverManager.getConnection(url, username, password);
}catch (Exception e){
e.printStackTrace();
}
}
public Connection getConnection(){
return connection;
}
}
若此时多线程调用MyObject.ConnectionFactory.getConnection()
方法,都是返回同一个连接