浅淡java单例模式结合多线程测试

  本人才疏学浅,正好利用博客这个平台整理下思路

  使用单例模式简单来说生成对象时属性都一样,即你new一百次,通过方法得到的结果都一样(比如获取静态资源文件,工具类等). 所以就没必要生成多个对象浪费服务器内存,他和静态类又不同,因为单例本质也是对象系统,长期不使用,也会给cg清除.但是静态类不同,静态类的成员变量和有静态方法会在程序的整个生命周期存在,比如在服务器内在中加载后服务器不关,就会一直存在,同理的有servlet的ServletContext对象和jsp的application对象

  单例模式核心就是一个类中,只生成一个这个类的对象.

  下面是最原始的懒汉模式(懒汉就是在类加载时不自动初始化单例对象,你需要时手动生成)

/**
 * Created by wyh on 3/4/2017.
 */
public class Sington {
    private static Sington sington;
    public static int identifyCode; //插入一个验证码验证时是否生成了多个对象
    //private Test test; 插入你想要组合的对象

    private Sington(){
        //code.. 对你组合的对象初始化
    }
    public static Sington getInstance(){
        if(sington==null){
            sington=new Sington();
            identifyCode++;
        }
        return sington;
    }
    public void method(){
        //code..
    }
}

  

 和一般构造类对象不同的是,他屏蔽了构造器(构造器隐式的指定了static,详见thing in java),只提供一个静态的生成对象getInstance,然后在这个方法中控制对象生成的个数.

  如果存在两个刚好用户同时访问这个方法呢?那还是单例么?

  这里可以利用简单线程做个测试:

/**
 * Created by wyh on 3/4/2017.
 */
public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Sington.getInstance());
    }
    public static void main(String[] args){
        int i = 0;
        while(Sington.identifyCode!=2){  //如果验证码变成2,就表明生成了两个对象
            for(int j=0;j<2;j++ ) //模拟两个用户同时访问这个方法
                new MyThread().start();
            i++;
        }
        System.out.println(i+"次后,生成了第二个sington对象");
    }
}

  经过测试,大概有两次出现了第二个对象,分别是外循环6次和14次,其余时间没有.99%情况下都不会出现.

  这是个什么现象呢?我理解的就是两个线程(两个用户),刚好在同一个时间访问了这个方法,cpu执行数据操作是单线程的,只是速度极快,纳秒级别把高低电平从这个寄存器传递到那个寄存器或者是内存中这样传递,里面有一个寄存器存储着下次要传递数据的内存或者寄存器地址(学了汇编就能明白).回到正题,两个线程同时进入,这个同时其实必定有先后顺序的,上面说了,cpu是单线程的,不是多线程.前面进入的线程并没有完全跑完方法,下一个线程又进入了,执行了一部分数据运算,然后回到剩下的线程,所以会出现的生成两个对象.

  那么如何避免呢?

  第一种是直接在方法名中加上synchronize,简单来说就是给这个方法上锁,如果上一个线程没跑完,其他线程就不能访问这个方法

public  synchronized static Singleton getInstance(){
        if(sington==null){
            sington=new Singleton();
            identifyCode++;
        }
        return sington;
    }

  但是上面说了,99%情况下都不会遇到出现第二个对象,这么做不能发挥cup最大效能,有损效率,即是单线程,不能发挥多线程的优势

  懒汉模式还有一个双重校验的写法

public static Singleton getInstance(){
        if(singleton==null){  //第一次判断是否为null
            synchronized(Singleton.class){  //在代码块中加同步锁 如果已经有线程访问 当前线程转为阻塞状态
                    if(singleton==null){ //当第二个线程访问时 已经不为null了 那么不再创建对象
                        singleton=new Singleton();
                    }
                }
            }
            return singleton;
        }

  

  下面来个测试:

/**
 * Created by wyh on 3/4/2017.
 */
public class MyThread extends Thread {
    private int anInt;
    public MyThread(int i){
        this.anInt=i;
    }
    @Override
    public void run() {
        Singleton.getInstance(anInt);
    }
    public static void main(String[] args){
       for(int i = 0;i<3;i++){
        new MyThread(i).start();
       }
    }
}
/**
 * Created by wyh on 3/4/2017.
 */
public class Singleton {
    private static Singleton singleton;
    public static int identifyCode; //插入一个验证码验证时是否生成了多个对象
    //private Test test; 插入你想要组合的对象

    private Singleton(){
        //code.. 对你组合的对象初始化
    }

    public static Singleton getInstance(int i){
        System.out.println("enter method"+i);
        if(singleton==null){  //第一次判断是否为null
            System.out.println("status 1 thread---"+i);
            synchronized(Singleton.class){  //在代码块中加同步锁 如果已经有线程访问 当前线程转为阻塞状态
                    if(singleton==null){ //当第二个线程访问时 已经不为null了 那么不再创建对象
                        System.out.println("status 2 thread---"+i);
                        singleton=new Singleton();
                    }
                }
            }
            return singleton;
        }
    public void method(){
        //code..
    }
}

  运行结果为:

  我是模拟了三个线程同时进入这个方法,可以看到"enter method 0 1 2" 表示这三个线程都进入了方法

  "status 1 thread 0 1" 却有两个线程进入了

 if(singleton==null){
  //code..
}

  

为什么会有两个呢?这就是相当于两个用户恰好同时访问了这个方法"status 2 thread 0"表明只有一个线程进入了



synchronized(Singleton.class){
                    //code..
                }

  


这是加了同步锁的原因 如果能有两个 那就奇了怪了 汗这就实现了单例对象

  同样,所有线程都进入了方法,只有一个线程进入了外层的

 if(singleton==null){
  //code..
}

  

这表明这个线程运算的很快,得到了cpu亲睐,让他至少的运行到了生成对象的位置
singleton=new Singleton();
其他两个线程在进入时,先行者已经生成了对象,自然也就不能进入对象为null的代码块了

  这个结果同理

  main方法是一个主线程,

里面的代码是子线程,这又可以理解你机器在开启这个主线程的时候

cpu同时也在跑着其他应用的线程.

public static Singleton getInstance(int i){
        System.out.println("enter method"+i);
        if(singleton==null){  //第一次判断是否为null
            System.out.println("status 1 thread---"+i);
            synchronized(Singleton.class){  //在代码块中加同步锁 如果已经有线程访问 当前线程转为阻塞状态
                    if(singleton==null){ //当第二个线程访问时 已经不为null了 那么不再创建对象
                        System.out.println("status 2 thread---"+i);
                        singleton=new Singleton();
                    }
                }
            }

  

 线程执行顺序是:

  thread 2 第一个进入方法 执行了输出语句 然后变为阻塞状态

  thread 0 第二个进入方法  执行了输出语句  然后变为阻塞状态

  thread 1 第二个进入方法  执行了输出语句  然后变为阻塞状态

  这时

  thread 1 第一个进入status 1  执行了输出语句 然后再次阻塞

  thread 2 第二个进入status 1 执行了输出 然后再次变为阻塞

  下面

  thread 0 cpu比较看好他在同步锁代码块中让他第一个进入 因为加入了synchronized关键字,那么他在当前主线程中

  一定会执行完代码块 然后轮到其他线程 这和平时写的单线程main方法同理

  最后

  thread 1 最后才进入 status 1 其他thread 0 2 都已经进入过了

  但是为什么thread 1 还能进入status 1 即

if(singleton==null)  //第一次判断是否为null
            System.out.println("status 1 thread---"+i);

thread 0 不是已经生成了对象了么?

  其实他在早些时候已经进入了status 1 只是cpu后面才调试他输出这句话

  一条语句可以理解为线程运行的一个结点 一个单位 但我相信 实际cpu来回操作不同线程

数据要细分的多,cpu每秒运行十几亿次啊! 你看到的这个结果 是cpu来回操作数据多少次后的结果

综上所述 其线程(小的数据运行块)是cpu主动调度的结果 而每个cpu都有他自己的一套调度规则

才得以支撑window上数百个进程 上千个线程孜孜不倦的运行着

你看着有前台程序和后台程序 只是他速度太快 快到你以为是同时运行着的结果

这和flash一个道理 多个静态的图片在能够欺骗人肉眼的时间内极快的切换完 每秒30帧 60帧

这就形成了你看到的动画效果了

在多线程中同样的道理,只是他这个快 数以亿计.

因为cpu只有一个cpu啊,他只有那么多个支撑他运算的几个寄存器和一个逻辑内存空间

除非你有多个cpu 才能实现真正的同步运算

回到单例模式,这种双重校验的办法

相比于整个方法synchronized效率会高一些;

在关键代码处加上锁 使其成为单线程,

其他块同步进行,相对于上面的锁效率会高一些

以上就是我介绍的两种简单单例模式的样本 欢迎指教

  

 
时间: 2024-10-17 18:43:36

浅淡java单例模式结合多线程测试的相关文章

Java - 单例模式与多线程

单例模式大家并不陌生,分为饿汉式和懒汉式等. 线程安全的饿汉式单例 饿汉式单例在类第一次加载的时候就完成了初始化,上代码: public class MyObject { private static MyObject myObject = new MyObject(); public static MyObject getInstance(){ return myObject; } } 下面来验证饿汉式单例的线程安全性: public class MyThread extends Thread

java单例模式,多线程下实现

单例模式 必备条件: 1:private的构造方法. 2:private static 对象保存该类实例. 3:static方法返回该类实例. (一)饿汉模式 /** * 单例模式 * 1:线程安全实现 * 2:浪费内存 * @author 祥少 * */public class SingletonTest { //final关键字,导致一旦被初始化,一直占用该段内存(即使你不使用)    public static final SingletonTest singletonTest = new

Java多线程核心技术(五)单例模式与多线程

本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 new 实例化. public class MyObject { private static MyObject myObject = new MyObject(); public MyObject(){ } public static MyObject getInstance(){ return myObj

设计模式——单例模式(Java)——考虑多线程环境下的线程安全问题

设计模式--单例模式(Java)--考虑多线程环境下的线程安全问题 一:单例模式概念 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例 二:单例模式的实现方式 特别注意,在多线程环境下,需要对获取对象实例的方法加对象锁(synchronized) 方式一:(懒汉式)程序执行过程中需要这个类的对象,再实例化这个类的对象 步骤: 1.定义静态私有对象 2.构造方法私有化保证在类的外部无法实例化该类的对象 3.定义对外开放的静

单例模式与多线程

概述 关于一般单例模式的创建和分析在我的另一篇博客<Java设计模式--单件模式>中有详细说明.只是在上篇博客中的单例是针对于单线程的操作,而对于多线程却并不适用,本文就从单例模式与多线程安全的角度出发,讲解单例模式在多线程中应该如何被使用. 版权说明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 本文作者:Coding-Naga 发表日期: 2016年4月6日 本文链接:http://blog.csdn.net/lemon_tree12138/article/det

单例模式 和 多线程

饿汉模式又称为立即加载模式,含以上就是非常急 也就是在使用类的时候已经将对象创建完毕 package lock; public class EhanSingleton { /*饿汉加载模式/立即加载模式*/ //初始化构造函数 private EhanSingleton(){ } private static EhanSingleton ehan = new EhanSingleton(); public static EhanSingleton getInstance(){ try { Thr

Java 单例模式探讨

以下是我再次研究单例(Java 单例模式缺点)时在网上收集的资料,相信你们看完就对单例完全掌握了 Java单例模式应该是看起来以及用起来简单的一种设计模式,但是就实现方式以及原理来说,也并不浅显哦. 总结一下我所知道的单例模式实现方式: 1.预先加载法 Java代码 class S1 { private S1() { System.out.println("ok1"); } private static S1 instance = new S1(); public static S1

你所知道的Java单例模式并不是单例模式

当我们搜索单例模式的时候,能看到很多例子,什么懒汉式.饿汉式,大概如下: public class Singleton { private static Singleton instance=null; private Singleton(){ System.out.println("Singleton..init..."); } public static Singleton getInstance(){ if(instance==null){ instance=new Single

浅谈java类集框架和数据结构(2)

继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主要有最重要的三种实现:ArrayList,Vector,LinkedList,三种List均来自AbstracList的实现,而AbstracList直接实现了List接口,并拓展自AbstractCollection. 在三种实现中,ArrayList和Vector使用了数组实现,可以认为这两个是