立即加载是指在使用类的时候对象已经创建完毕,常见的实现方法是直接new实例化。在立即加载/饿汉模式中,调用方法前,实例已经被创建了,下面来看一下实现代码:
package com.single.thread;
public class SingleInstance {
private static SingleInstance instance=new SingleInstance();
private SingleInstance(){
}
public static SingleInstance getInstance(){
return instance;
}
}
class test{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
测试结果:
2084620955
2084620955
2084620955
控制台输出的结果看出hashcode是一个值完成了立即加载的单例模式。但是缺点是不能拥有其他的实例变量,因为getinstance方法不是同步的,所以可能出现非线程安全问题。
延迟加载是指调用方法的时候对象才实例化。
package com.single.thread;
public class SingleInstance2 {
private static SingleInstance2 instance;
private SingleInstance2(){
}
public static SingleInstance2 getInstance(){
if(instance==null){
instance=new SingleInstance2();
}
return instance;
}
}
class test2{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance2.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
34369211
1712447624
1300234150
控制台输出的hashcode不同说明创建了三个对象并不是单例的。
只需要在getinstance方法上加上同步关键字synchronized
package com.single.thread;
public class SingleInstance2 {
private static SingleInstance2 instance;
private SingleInstance2(){
}
public synchronized static SingleInstance2 getInstance(){
if(instance==null){
instance=new SingleInstance2();
}
return instance;
}
}
class test2{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance2.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
1746582977
1746582977
1746582977
此方法加上synchronized关键字得到了相同的实例对象,但是这种方法效率低,是同步运行的。下一个线程必须等上一个线程释放锁才能进入此方法。
能否使用同步代码块来解决下面来看一个案例
package com.single.thread;
public class SingleInstance3 {
private static SingleInstance3 instance;
private SingleInstance3(){
}
public static SingleInstance3 getInstance(){
try {
if(instance==null){
Thread.sleep(200);
synchronized (SingleInstance3.class) {
instance = new SingleInstance3();
}
}
}catch (Exception e){
e.printStackTrace();
}
return instance;
}
}
class test3{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance3.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
1877212914
847086262
1688623490
此方法使用同步synchronized语句块对实例化对象的关键代码进行同步,运行效率得到了提升,但是从结果上来看并没有解决多线程问题。下面使用
双检查锁机制来解决多线程下加载饿汉单例模式。
package com.single.thread;
public class SingleInstance4 {
private volatile static SingleInstance4 instance;
private SingleInstance4(){
}
public static SingleInstance4 getInstance(){
try {
if(instance==null){
Thread.sleep(200);
synchronized (SingleInstance4.class) {
if(instance==null)
instance = new SingleInstance4();
}
}
}catch (Exception e){
e.printStackTrace();
}
return instance;
}
}
class test4{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance4.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
1688623490
1688623490
1688623490
使用volatile关键字可以使instance变量在多线程间达到可见性,另外也会禁止指令重排,因为 instance = new SingleInstance4()代码在内存部分为三个部分
1)memory=allocate(); //分配对象的内存空间
2) ctorInstance(memory); //初始化对象
3)instance=memory; //设置instance指向刚分配的内存地址
JIT编译器有可能将这三个步骤重排序成:
1)memory=allocate(); //分配对象的内存空间
2) instance=memory; //设置instance指向刚分配的内存地址
3)ctorInstance(memory); //初始化对象
这时候将会出现以下情况:虽然构造方法还没有执行,但是instance对象有了内存地址,值不是null,当访问instance对象中的实量还是默认数据类型的默认值。
public class SingleInstance5 {
private SingleInstance5(){
}
private static class InnerInstance{
private static SingleInstance5 instance=new SingleInstance5();
}
public static SingleInstance5 getInstance(){
return InnerInstance.instance;
}
}
class test5{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance5.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
38910761
38910761
38910761
从控制台输出的结果来看可以知道静态内部类可以实现单例模式。
public class User {
}
package com.single.xue;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class MyObject implements Serializable {
private static final long serialVersionUID=666L;
public static User user=new User();
private static MyObject myObject=new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
//该方法在反序列化时不创建新的instance对象
protected Object readResolve() throws ObjectStreamException {
System.out.println("该方法被调用了");
return MyObject.myObject;
}
}
测试类:
import java.io.*;
public class Test {
public static void main(String[] args) {
MyObject instance = MyObject.getInstance();
FileOutputStream fileOutputStream=null;
ObjectOutputStream objectOutputStream=null;
FileInputStream fileInputStream=null;
ObjectInputStream objectInputStream=null;
try {
System.out.println("序列化——instance=" + instance.hashCode() + "\t" +
"user=" + MyObject.user.hashCode());
fileOutputStream = new FileOutputStream(new File("e:\\myObject.txt"));
objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance);
fileOutputStream.close();
objectOutputStream.close();
}catch (Exception e){
e.printStackTrace();
}
try{
fileInputStream=new FileInputStream(new File("e:\\myObject.txt"));
objectInputStream=new ObjectInputStream(fileInputStream);
MyObject object = (MyObject)objectInputStream.readObject();
fileInputStream.close();
objectInputStream.close();
System.out.println("序列化——instance=" + instance.hashCode() + "\t" +
"user=" + MyObject.user.hashCode());
}catch (Exception e){
e.printStackTrace();
}
}
}
结果:
序列化——instance=557041912 user=1134712904
该方法被调用了
序列化——instance=557041912 user=1134712904
package com.single.thread;
public class SingleInstance6 {
private static SingleInstance6 instance=null;
static {
instance=new SingleInstance6();
}
private SingleInstance6(){
}
public static SingleInstance6 getInstance(){
return instance;
}
}
class test6{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(SingleInstance6.getInstance().hashCode());
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
566612817
566612817
566612817
静态代码块在使用类的时候就已经执行,所有可以应用静态代码块的这个特性来实现单例。
public class MyObject {
public enum myEnumSingleton{
users;
private User user;
private myEnumSingleton() {
user=new User();
}
public User getUser(){
return user;
}
}
public static User getUser(){
return myEnumSingleton.users.getUser();
}
}
class User{
}
class Tests{
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println(MyObject.getUser().hashCode());
}
}
});
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
370102716
370102716
370102716
370102716
370102716
370102716
enum枚举类型的特性和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,所有可以用来实现单例模式。