单例模式是最简单的设计模式之一,属于创建型模式,它提供了一种创建对象的方式,确保只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。
单例模式的解决的痛点就是节约资源,节省时间从两个方面看:
1.由于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是很重要的.
2.因为不需要频繁创建对象,我们的GC压力也减轻了,而在GC中会有STW(stop the world),从这一方面也节约了GC的时间
单例模式的缺点:简单的单例模式设计开发都比较简单,但是复杂的单例模式需要考虑线程安全等并发问题,引入了部分复杂度。
常见的单例模式有懒汉模式 和饿汉模式
所谓的懒汉模式就是什么时候我们要用对象时再进行创建对象,属于一种延迟加载的模式。通俗来讲,懒汉一直会等到实在饿得不行的时候才会进行进食。
所谓的饿汉模式它会提前将我们需要的东西给加载出来,有什么东西就提前进行消耗的意思。
下面简单的代码介绍懒汉和饿汉是如何进行创建的。
class hungryBoy{
//随着类的加载而创建对象 即提前创建对象
private static hungryBoy lazyPerson = new hungryBoy();
private static int bread=10;
private hungryBoy() {
}
public static hungryBoy getInstance(){
return lazyPerson;
}
}
//懒汉模式
class lazyBoy{
//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
private static lazyBoy lazyBoy = null;
public static lazyBoy getInstance(){
if(lazyBoy==null){
lazyBoy = new lazyBoy();
return lazyBoy;
}else{
return lazyBoy;
}
}
public lazyBoy() {
}
}
测试多线程情况下饿汉模式是否是线程安全的。
//创建多线程例子 调用单例模式的对象。查看其中的hashcode看是否相等
class demo implements Runnable{
@Override
public void run() {
System.out.println(hungryBoy.getInstance().hashCode());
}
}
main 方法运行多线程
public static void main(String[] args) {
//多线程测试单例模式线程是否安全
demo[] d = new demo[10];//线程数组
for(int i = 0;i<10;i++){
d[i]= new demo();//创建线程
}
for(int i=0;i<10;i++){
new Thread(d[i]).start();//线程启动
}
}
结果显示, 从下图可以看出懒汉模式所使用的对象的hashcode是一样的,简介的说明了所使用的的是同一个对象。原因:因为加载类的时候已经在堆里面创建好对象了。因而在程序运行的时候,对象肯定不为空。
class lazyBoy{
//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
private static lazyBoy lazyBoy = null;
public static lazyBoy getInstance(){
if(lazyBoy==null){
try {
Thread.sleep(100);//设置延迟时间
} catch (InterruptedException e) {
e.printStackTrace();
}
lazyBoy = new lazyBoy();
}
return lazyBoy;
}
public lazyBoy() {
}
}
class demo1 extends Thread{
@Override
public void run() {
System.out.println(lazyBoy.getInstance().hashCode());
}
}
public static void main(String[] args) {
//多线程测试单例模式线程是否安全
demo1[] d = new demo1[10];//线程数组
for(int i = 0;i<10;i++){
d[i]= new demo1();//创建线程
}
for(int i=0;i<10;i++){
new Thread(d[i]).start();//线程启动
}
}
结果如下 可以看出在该多线程数组进行并发执行的时候 创建了很多对象,这样就无法实现单例模式了。
class lazyBoy{
private static int bread=100;
private static lazyBoy lazyBoy = null;//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
public static synchronized lazyBoy getInstance(){
if(lazyBoy==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazyBoy = new lazyBoy();
}
return lazyBoy;
}
public lazyBoy() {
}
}
class lazyBoy{
private static int bread=100;
private static lazyBoy lazyBoy = null;//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
public static lazyBoy getInstance(){
try {
synchronized (lazyBoy.class) {//对代码块进行同步
if (lazyBoy == null) {
Thread.sleep(100);
lazyBoy = new lazyBoy();
}
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
return lazyBoy;
}
}
public lazyBoy() {
}
}
3.双检查锁机制 加上volatile 增加线程之间的可见性。在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性。
class lazyBoy{
private static int bread=100;
private volatile static lazyBoy lazyBoy = null;//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
public static lazyBoy getInstance(){
try {
if(lazyBoy==null){
synchronized(lazyBoy.class){
if (lazyBoy == null) {
Thread.sleep(100);
lazyBoy = new lazyBoy();
}
}
}
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
return lazyBoy;
}
}
public lazyBoy() {
}
}