多线程与单例模式
发布日期:2021-05-10 07:25:37 浏览次数:12 分类:精选文章

本文共 10136 字,大约阅读时间需要 33 分钟。

立即加载/饿汉模式

立即加载是指在使用类的时候对象已经创建完毕,常见的实现方法是直接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();    }}测试结果:208462095520846209552084620955

控制台输出的结果看出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();    }}结果:3436921117124476241300234150

控制台输出的hashcode不同说明创建了三个对象并不是单例的。

懒汉模式的解决方案

只需要在getinstance方法上加上同步关键字synchronizedpackage 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();    }}结果:174658297717465829771746582977

此方法加上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();    }}结果:18772129148470862621688623490

此方法使用同步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();    }}结果:168862349016886234901688623490

使用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();    }}结果:389107613891076138910761

从控制台输出的结果来看可以知道静态内部类可以实现单例模式。

序列化与反序列化的单列模式实现

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();    }}结果:566612817566612817566612817

静态代码块在使用类的时候就已经执行,所有可以应用静态代码块的这个特性来实现单例。

使用enum枚举数据类型来实现单例模式

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();    }}结果:370102716370102716370102716370102716370102716370102716

enum枚举类型的特性和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,所有可以用来实现单例模式。

上一篇:java多线程实现生产者消费者问题
下一篇:全面了解Java Timer定时器类

发表评论

最新留言

不错!
[***.144.177.141]2025年04月23日 17时00分57秒