Java多线程编程6--单例模式与多线程--单例模式设计详解1

在标准的23个设计模式中,单例设计模式在应用中是比较常见的。但在常规的该模式教学资料介绍中,多数并没有结合多线程技术作为参考,这就造成在使用多线程技术的单例模式时会出现一些意想不到的情况,这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果。

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

什么是立即加载?立即加载也称为“饿汉模式”,就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化。

立即加载/“饿汉模式”是在调用方法前,实例已经被创建了,来看一下实现代码。

/**
 * 此代码为立即加载,缺点是不能有其他实例变量
 * 因为该getInstance()方法没有同步,可能会出现非线程安全剖一
 */
public class MyObject {
    private static MyObject myObject = new MyObject();

    private MyObject(){}

    public static MyObject getInstance() {
      return myObject;
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}
public class Run {
    public static void main(String[] args) throws ParseException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}
1032010069

1032010069

1032010069

控制台打印的hashCode是同一个值,说明对象是同一个,也就实现了立即加载型单例设计模式。

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

什么是延迟加载?延迟加载也称为“懒汉模式”,就是在调用get()方法时实例才被创建,常见的实现办法就是在get()方法中进行new实例化。

延迟加载/“懒汉模式”是在调用方法时实例才被创建。虽然在一个线程中只有取一个实例,但如果在多线程的环境中,就会取出多个实例。

public class MyObject {
    private static MyObject myObject;

    private MyObject(){}

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

自定义线程和Run类同上,结果

658705244

580835623

1791140146

控制台打印出了3种hashCode,说明创建出了3个对象,并不是单例的,这就是“错误的单例模式”。如何解决呢?先看一下解决方案。

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

(1)声明synchronized关键字

既然多个线程可以同时进人getInstance()方法,那么只需要对getInstance()方法声明synchronized关键字即可。

public class MyObject {
    private static MyObject myObject;

    private MyObject(){}

    /**
     * 此种synchronized锁方法的运行效率非常低下,虽然是同步运行的,但每次调用getInstance(),都要对对象上锁
     * 而且下一个线程想要取得对象,则必须等上一个线程释放锁之后,才可以继续执行.
     */
    public synchronized static MyObject getInstance() {
        if (myObject == null) {
            //模拟在创建2对象之前做一些准备性的工作
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myObject = new MyObject();
        }
        return myObject;
    }
}

自定义线程和Run类同上,结果

1791140146

1791140146

1791140146

2.2.尝试同步代码块

同步方法是对方法的整体进行持锁,这对运行效率不是不利的。改成同步代码块能解决吗?先看一个效率不太好的例子

public class MyObject {
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static MyObject myObject = null;

    private MyObject(){}

    public static MyObject getInstance() {
        try {
            /*此种写法等同于:
            * public synchronized static MyObject getInstance() 的写法
            * 效率很低
            * */
            synchronized (MyObject.class) {
                if (myObject == null) {
                    //模拟在创建对象之前做一些准备性的工作
                    Thread.sleep(3000);
                    myObject = new MyObject();
                }
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}

自定义线程和Run类同上,结果

658705244

658705244

658705244

同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅度提升。

使用DCL双检测锁机制来实现,继续修改这个代码:

public class MyObject {
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static MyObject myObject = null;

    private MyObject(){}

    //使用双检测机制来解决问题,既保证了不需要同步代码的异步执行性,又保证了单例的效果
    public static MyObject getInstance() {
        try {

            if (myObject == null) {
                //模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);

                //针对某些重要的代码进行单独的同步!!!
                synchronized (MyObject.class) {
                    //如果没有这个判断,在多线程的情况下无法到得同一个实例对象!!!
                    if (myObject == null) {
                        myObject = new MyObject();
                    }
                }
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myObject;
    }
}
1791140146

1791140146

1791140146

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

时间: 2024-11-08 15:40:36

Java多线程编程6--单例模式与多线程--单例模式设计详解1的相关文章

Java多线程编程中的lock使用源码详解

将做工程过程重要的代码段做个记录,如下的代码内容是关于Java多线程编程中的lock使用详解的代码,应该是对码农有帮助. import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.locks.Lock; import java.util.concurrent.l

JAVA基础编程50题(7-9题)详解

一.描述 1.输入一行字符,分别统计出其中英文字母.空格.数字和其它字符的总个数和每个字符出现的频率. 程序分析:使用String类的matchs()分别统计符合正则表达式的每类字符的总个数,然后分别使用List和Map集合类统计每个字符出现的频率. 2.求s=a+aa+aaa+aaaa+aa...a的值,其中a是一个数字.例如2+22+222+2222+22222(此时共有5个数相加),几个数相加由键盘控制. 3.题目:一个数如果恰好等于它的因子之和,这个数就称为"完数",即除了本身

JAVA基础编程50题(13-15题)详解

一.描述 1.一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少? 程序分析:在10万以内判断,先将该数加上100后再开方,再将该数加上168后再开方,如果开方后再平方等于原数则符合结果. 2.输入某年某月某日,判断这一天是这一年的第几天? 程序分析:以3月5日为例,应该先把前两个月的加起来,然后再加上5天即本年的第几天,特殊情况,闰年且输入月份大于3时需考虑多加一天. 3.输入三个整数x,y,z,请把这三个数由小到大输出. 程序分析:将最小的数放到x上,先

JAVA基础编程50题(22-24题)详解

一.描述 题目1:统计输入的一段字符串,分别统计这个字符串中大小写字母的个数,以及数字出现的次数. 第一种方法使用Character封装类的方法:isLowerCase(),isUpperCase(),isDigit()判断是否是该类字符, 第二种方法是直接使用char字符范围比较来统计. 题目2:用户输入一串待统计的字符串,然后输入用户想要统计的某个单词或者字符的次数. 比如我输入如下字符串:fdhiaojavajidaoijdjava 我要统计其中的java字符串的个数. 解题思路:传入待统

JAVA基础编程50题(25-27题)详解

一.描述 题目1:判断一个数字是否是2的阶次方数,例如8,16,64,256都是2的阶次方数. 题目解析:如果一个数是2的阶次方数,那么这个数字的二进制数的首位为1,后面跟着若干个0,例如8用二进制表示为1000,64为1000000, 如果让这个数减1,然后和这个数做按位&运算即得0,即(number-1)&number==0,8&7=1000&0111=0000. 题目2:列出一个数组中所有元素的组合,比如1.2.3列出来为1.12.123.13.132.2.21.21

JAVA基础编程50题(4-6题)详解

一.描述 1.将一个正整数分解质因数.例如:输入90,打印出90=2*3*3*5. 程序分析:对n进行分解质因数,应先找到一个最小的质数k,然后按下述步骤完成: (1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,输出之前的所有因子. (2)如果n!=k,但n能被k整除,则应打印出k的值,并用n除以k的商作为新的正整数n,重复执行第一步. (3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步. 2.利用条件运算符的嵌套来完成此题:学习成绩>=90分的同学用A表示,60-89分之

JAVA基础编程50题(10-12题)详解

一.描述 1.一球从m米高度自由落下,每次落地后反跳回原高度的一半:再落下,求它在 第n次落地时,共经过多少米?第10次反弹多高? 2.有1.2.3.4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少? 程序分析:可填在百位.十位.个位的数字都是1.2.3.4.但是必须满足每一位上的数字各不相同,根据排列组合原理总共有4*3*2=24种. 3.企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高

JAVA基础编程50题(1-3题)详解

一.题目描述 1.古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子对数为多少? 程序分析: 兔子的规律为数列1,1,2,3,5,8,13,21.... ,该题其实就是斐波那契数列的一种. 2.判断m-n之间有多少个素数,并输出所有素数. 程序分析:判断素数的方法:用一个数n分别去除2到sqrt(n),这里是Math自带的函数sqrt()求该数的平方根,如果能被整除,则表明此数不是素数,反之是素数. 3.打印出所有的

Java线程编程中isAlive()和join()的使用详解

一个线程如何知道另一线程已经结束?Thread类提供了回答此问题的方法. 有两种方法可以判定一个线程是否结束.第一,可以在线程中调用isAlive().这种方法由Thread定义,它的通常形式如下: ? 1 final boolean isAlive( ) 如果所调用线程仍在运行,isAlive()方法返回true,如果不是则返回false.但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下: ? 1 final void join( ) throws Inte

Java多线程编程总结一:多线程基本概念

Java多线程编程总结一 – 初识多线程 进程.多进程.线程.多线程的概念 进程(process):CPU的执行路径.通俗的说就是系统中正在运行的程序.比如我们打开了浏览器.QQ等等,这些程序一旦被打开运行了,就是所谓的进程. 多进程:系统中同时运行的多个程序.这个我们应该不难理解了,在打开浏览器的同时我们也可以QQ聊天.CS单机游戏等. 线程(thread):运行在进程中的运行单元.比如迅雷下载中我们的某一个下载任务就是一个线程. 多线程:同理可知,每个进程里面有多个独立的或者相互有协作关系的