单例模式与多线程(一)

1.立即加载/“饿汉模式”

  什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接new实例化。而立即加载从中文的语境上看,有“着急”、“急迫”的含义,所以也称为“饿汉模式”。
  立即加载/“饿汉模式”是在调用方法前,实例已被创建了。
  创建项目,名称为6.1,创建类MyObject.java代码如下:

public class MyObject {

    //立即加载==饿汉模式
    private static MyObject myObject = new MyObject();
    private MyObject(){
    }
    public static MyObject getInstance() {
        //此代码版本为立即加载
        //此版本代码的缺点是不能有其他实例变量
        //因为getInstance()方法没有同步
        //所以有可能出现非线程安全问题
        return myObject;
    }
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果如下所示:

2046608170
2046608170
2046608170

“饿汉模式”的运行结果

  控制台的打印看到hashcode是同一个值,说明对象是同一个,也就完成我们刚刚所要求的饿汉模式。

2.延迟加载/“懒汉模式”

  什么是延迟加载?延迟加载就是在调用get()方法时实例才被创建,常见的实现方法就是在get()方法的时候进行new实例化。而延迟加载从中文的语境来看,是“缓慢”、“不急迫”的含义,所以也称为“懒汉模式”。

1.延迟加载/“懒汉模式”解析

  延迟加载/“懒汉模式”是在调用方法时实例才被创建。
创建项目6.2.1,创建类MyObject.java代码如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject() {
    }
    public static MyObject getInstance() {
        //延迟加载
        if (myObject != null) {
        } else {
            myObject = new MyObject();
        }
        return myObject;
    }
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

程序运行结果如下所示:

1080578516

“懒汉模式”成功取出一个实例

  此实验虽然取得一个对象的实例,但如果是在多线程的环境中,就会出现取出多个实例的情况,与单例模式的初衷是相背离的。

2.延迟加载/“懒汉模式”的缺点

  前面两个实验虽然使用“立即加载”和“延迟加载”实现了单例设计模式,但在多线程的环境中,前面“延迟加载”示例中的代码完全就是错误的,根本不能实现保持单例的状态。来看一下如何在多线程环境中结合“错误的单例模式”创建出“多例”。
创建项目6.2.2,创建类MyObject.java代码如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject() {
    }
    public static MyObject getInstance() {
        try {
            if (myObject != null) {
            } else {
                //模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

程序运行结果如下所示:

35859381
943931288
35859381

非单例设计模式

  控制台打印出了3种hashCode,说明创建出了3个对象,并不是单例的,这就是“错误的单例模式”。

3.延迟加载/“懒汉模式”的解决方案

(1)声明synchronized关键字

  既然多个线程可以同时进入getInstance()方法,那么只需要对getInstance()方法声明synchronized关键字即可。
创建项目6.2.3-1,创建类MyObject.java代码如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject() {
    }
    // 设置同步方法效率太低了
    // 整个方法被上锁
    synchronized public static MyObject getInstance() {
        try {
            if (myObject != null) {
            } else {
                // 模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

程序运行结果如下所示:

569219718
569219718
569219718

  此方法加入同步synchronized关键字得到相同实例的对象,但此种方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,则必须等上一个线程释放锁之后,才可以继续执行。

(2)尝试同步代码块

  同步方法是对方法的整体进行持锁,这对运行效率来讲是不利的。
创建项目6.2.3-2,创建类MyObject.java代码如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject() {
    }
    public static MyObject getInstance() {
        try {
            // 此种写法等同于:
            // synchronized public static MyObject getInstance()
            // 的写法,效率一样很低,全部代码被上锁
            synchronized (MyObject.class) {
                if (myObject != null) {
                } else {
                    // 模拟在创建对象之前做一些准备性的工作
                    Thread.sleep(3000);
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
        //此版本代码虽然是正确的
        //但public static MyObject getInstance()方法
        //中的全部代码都是同步的了,这样做也会降低运行效率
    }
}

程序运行结果如下所示:

1674612146
1674612146
1674612146

  此方法加入同步synchronized语句块得到相同实例的对象,但这种方法的运行效率也是非常低的,和synchronized同步方法一样是同步运行的。

(3)针对某些重要的代码进行单独的同步

  同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅提升。
创建项目6.2.3-3,创建类MyObject.java代码如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject() {
    }
    public static MyObject getInstance() {
        try {
            if (myObject != null) {
            } else {
                // 模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                // 使用synchronized (MyObject.class)
                // 虽然部分代码被上锁
                // 但还是有非线程安全问题
                synchronized (MyObject.class) {
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

程序运行结果如下所示:

263494223
239871018
1662354276

  此方法使同步synchronized语句块,只对实例化对象的关键代码进行同步,从语句的结构上来讲,运行的效率的确得到了提升但如果是遇到多线程的情况下还是无法解决得到同一个实例对象的结果。

(4)使用DCL双检查锁机制

  在最后的步骤中,使用的是DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式。
创建项目6.2.3-4,创建类MyObject.java代码如下:

public class MyObject {
    private volatile static MyObject myObject;
    private MyObject() {
    }
    // 使用双检测机制来解决问题,既保证了不需要同步代码的异步执行性
    // 又保证了单例的效果
    public static MyObject getInstance() {
        try {
            if (myObject != null) {
            } else {
                //模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                synchronized (MyObject.class) {
                    if (myObject == null) {
                        myObject = new MyObject();
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
    // 此版本的代码称为双重检查Double-Check Locking
}

创建线程类MyThread.java代码如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}
//使用双重检查锁功能,成功地解决了“懒汉模式”遇到读哦现场

程序运行结果如下所示:

38472714
38472714
38472714

  使用双重检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。


以上代码下载请点击该链接:https://github.com/Yarrow052/Java-package.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值