浅淡java单例模式结合多线程测试
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅淡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效率会高一些;
在关键代码处加上锁 使其成为单线程,
其他块同步进行,相对于上面的锁效率会高一些
以上就是我介绍的两种简单单例模式的样本 欢迎指教
以上是关于浅淡java单例模式结合多线程测试的主要内容,如果未能解决你的问题,请参考以下文章