单例模式与多线程

长乐钟声花外尽,龙池柳色雨中深
单例模式一般分为立即加载和延迟加载,通俗的说就是“饿汉模式”和“懒汉模式”。
立即加载/“饿汉模式”:立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是new实例化。
测试代码:

public class SingleTest {
    //立即加载==恶汉模式
    private static SingleTest singleTest = new SingleTest();
    public SingleTest() {

    }
    public static SingleTest getInstance() {
        return singleTest;
    }
}

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

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

运行结果:
这里写图片描述
由此可见,我们实现了立即加载的单例模式。
延迟加载/“懒汉模式” :延迟加载就是在调用get()方法时实例才被创建,常见的办法就是在get()方法中进行new实例化。
测试代码:


public class SingleTest {
    private static SingleTest singleTest;
    public SingleTest() {

    }
    public static SingleTest getInstance() {
        //延迟加载==懒汉模式
        if (singleTest != null) {

        }else {
            singleTest = new SingleTest();
        }
        return singleTest;
    }
}

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

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

运行结果:
这里写图片描述
这样虽然是获取到了一个实例,但是这里是有严重问题,我们在该例中只有一个县城,要是多线程的情况下,就会实例出多个对象,继续测试:

public class SingleTest {
    private static SingleTest singleTest;
    public SingleTest() {

    }
    public static SingleTest getInstance() {
        try {
            //延迟加载==懒汉模式
            if (singleTest != null) {

            }else {
                //模拟在创建对象之前做的一些准备工作
                Thread.sleep(3000);
                singleTest = new SingleTest();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return singleTest;
    }
}

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

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

运行结果:
这里写图片描述
出现了多个实例,这里就违反了单例模式的意义。
那么,延迟加载的在多线程的环境下如何才能保证单例呢?首先想到应该是加入synchronized关键字,既然多个线程都要进入getInstance()方法,那么给getInstance()方法加上synchronized关键字。
测试代码:

public class SingleTest {
    private static SingleTest singleTest;
    public SingleTest() {

    }
    //就是加了synchronized关键字
    synchronized public static SingleTest getInstance() {
        try {
            //延迟加载==懒汉模式
            if (singleTest != null) {

            }else {
                //模拟在创建对象之前做的一些准备工作
                Thread.sleep(3000);
                singleTest = new SingleTest();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return singleTest;
    }
}

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

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

运行结果:
这里写图片描述
这里虽然是解决了延迟加载多线程下的单例模式,但是想到加了synchronized关键字之后的getInstance() 方法变成同步了,下一个线程要想获取到对象,就必须等上一个线程释放锁之后才行,这样大大降低了效率。如果这时你想只同步某些重要的代码,以此来提高效率,继续测试:

public class SingleTest {
    private static SingleTest singleTest;
    public SingleTest() {

    }
     public static SingleTest getInstance() {
        try {
            //延迟加载==懒汉模式
            if (singleTest != null) {

            }else {
                //模拟在创建对象之前做的一些准备工作
                Thread.sleep(3000);
                //使用synchronized (SingleTest.class)同步一些代码
                synchronized (SingleTest.class) {
                    singleTest = new SingleTest();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return singleTest;
    }
}

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

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

运行结果:
这里写图片描述
这样虽然效率提高了,但并非单例了。
难道,延迟加载和多线程就不能较好的实现单例模式了吗?很显然,答案是有的。那就是使用DCL(Double-Check Locking)双检查锁机制。
测试代码:

public class SingleTest {
    private volatile static SingleTest singleTest;  //加入volatile关键字
    public SingleTest() {

    }
     public static SingleTest getInstance() {
        try {
            //延迟加载==懒汉模式
            if (singleTest != null) {

            }else {
                //模拟在创建对象之前做的一些准备工作
                Thread.sleep(3000);
                //使用synchronized (SingleTest.class)同步一些代码
                synchronized (SingleTest.class) {
                    if (singleTest == null) {
                        singleTest = new SingleTest();
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return singleTest;
    }
}

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

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

运行结果:
这里写图片描述
使用双重检查锁功能,成功的解决了延迟加载的多线程问题。同时,DCL也是大多数多线程结合单例模式使用的解决方案。
当然,也可以使用static代码块来实现单例模式。因为静态代码块在使用类的时候就已经执行了,所以利用这个特性来实现单例模式。
测试代码:

public class SingleTest {
    private static SingleTest singleTest = null;
    public SingleTest() {

    }
    static {
        singleTest = new SingleTest();
    }
     public static SingleTest getInstance() {
        return singleTest;
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0 ;i < 3 ; i++) {
            System.out.println(SingleTest.getInstance().hashCode());
        }
    }
}

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

运行结果:
这里写图片描述
使用enum枚举数据类型实现单例模式:因为枚举enum和静态代码块的特性相似,在使用枚举是,构造方法会被自动调用,也可以用这个特性来实现单例模式。
测试代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SingleTest {
    public enum SingleEnumTest {
        connectionFactory;
        private Connection connectionn;

        private SingleEnumTest() {
            try {
                System.out.println("调用了SingleTest的构造方法");
                String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8";
                String username = "root";
                String password = "root";
                String driverName = "com.mysql.jdbc.Driver";
                Class.forName(driverName);
                connectionn = DriverManager.getConnection(url, username, password);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public Connection getConnectionn() {
            return connectionn;
        }
    }
    public static Connection getConnection() {
        return SingleEnumTest.connectionFactory.getConnectionn();
    }
}


class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0;i < 5; i++) {
            System.out.println(SingleTest.getConnection().hashCode());
        }
    }
}

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

这里写图片描述
这样,也实现了单例模式。

猜你喜欢

转载自blog.csdn.net/A_Runner/article/details/80724618