java的单例详解
来源:互联网 发布:网络没问题app网络异常 编辑:程序博客网 时间:2024/06/07 14:08
今天我的一个开发项目可能需要用到单例来实现一些功能,因此在网上各大博客看到许多的关于java多线程的单例模式,虽然网上有许多这方面的博客,但是这是我通过学习和自己实验理解之后总结出来的,不一定适合所有人。
单例的几种写法
1、懒汉式(线程不安全)[不可用]
实验代码
package test;public class Test { public static void main(String[] args) { A obj1 = new A(); B obj2 = new B(); obj1.start(); obj2.start(); } }class T{ public int b ; private T(){ b = 1; } private static T obj; public static T getinstance(){ if (obj==null){ obj = new T(); } return obj; }}class A extends Thread{ @Override public void run(){ T obj1= T.getinstance(); for(int i=0;i<10;i++){ System.out.println("A"+obj1.b); obj1.b++; } }}class B extends Thread{ @Override public void run(){ T obj2 = T.getinstance(); for(int i=0;i<10;i++){ System.out.println("B"+obj2.b); obj2.b++; } }}
实验结果:
run:
B1
A1
B2
A2
B3
A3
A4
A5
B4
B5
A6
B6
A7
B7
B8
A8
B9
A9
B10
A10
由以上结果显示,T类创建了两个完全不相关的对象。这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (obj== null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。很明显A、B就是这种情况,产生了两个不相关的实例。所以在多线程环境下一定不可使用这种方式。
这里我要说明一下Lazy Loading的含义:从字面解释就是懒惰加载,其实这个解释并不准确,应该是延迟加载,就是不用类的实例不用在初始化装载,只有到使用的时候才加载。
2、懒汉式(线程安全,同步方法)[不推荐用]
这种方法就是在getinstance方法添加同步锁
实验代码:
package test;public class Test { public static void main(String[] args) { A obj1=new A(); B obj2 = new B(); obj1.start(); obj2.start(); } }class T{ public int b ; private T(){ b = 1; } private static T obj; public static synchronized T getinstance(){ if (obj==null){ obj = new T(); } return obj; }}class A extends Thread{ @Override public void run(){ T obj1= T.getinstance(); for(int i=0;i<10;i++){ System.out.println("A"+obj1.b); obj1.b++; } }}class B extends Thread{ @Override public void run(){ T obj2 = T.getinstance(); for(int i=0;i<10;i++){ System.out.println("B"+obj2.b); obj2.b++; } }}
实验结果:run:
A1
B1
A2
B3
A4
B5
A6
B7
B9
A8
B10
A11
B12
A13
B14
A15
B16
A17
B18
A19
可见这种也能实现多线程的单例安全,但是就是效率太低了,每个线程在想获得类的实例时候,执行getinstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。这是只有两个线程竞争,如果在分布式中可能有多个线程竞争效率会更低。
3、懒汉式(同步代码块)[视情况而定,但如果可用也会降低效率同样不推荐使用]
实验代码:
package test;public class Test { static void main(String[] args) { A obj1=new A(); B obj2 = new B(); obj1.start(); obj2.start(); } }class T{ public int b ; private T(){ b = 1; } private static T obj; public static T getinstance(){ if (obj==null){ //synchronized(T.class){ // if (obj==null){ synchronized(T.class){ // obj = new T(); obj = new T(); // } } //} } return obj; }}class A extends Thread{ @Override public void run(){ T obj1= T.getinstance(); for(int i=0;i<10;i++){ System.out.println("A"+obj1.b); obj1.b++; } }}class B extends Thread{ @Override public void run(){ T obj2 = T.getinstance(); for(int i=0;i<10;i++){ System.out.println("B"+obj2.b); obj2.b++; } }}
结果:
run:
B1
A1
B2
A2
B3
A3
B4
B5
A4
A5
A6
A7
B6
B7
B8
A8
B9
A9
B10
A10
同一一样。但是有趣的是,作者如果把整个if语句同步(像注释中一样的写法),能够保证单例安全。因此在这个例子中关键还是A、B两个线程竞争if判断语句。同时这个写法也会降低效率。我看到网上的博客还有双重检查通过两次检查,其中第二次检查同步来保证线程安全。但是我觉得没有我注释中写的效率高,只进行了一次检查。
4、饿汉式(静态常量)[可用]
实验代码:
package test;public class Test { public static void main(String[] args) { A obj1=new A(); B obj2 = new B(); obj1.start(); obj2.start(); } }class T{ public int b ; private T(){ b = 1; } private static final T obj = new T(); public static T getinstance(){ return obj; }}class A extends Thread{ @Override public void run(){ T obj1= T.getinstance(); for(int i=0;i<10;i++){ System.out.println("A"+obj1.b); obj1.b++; } }}class B extends Thread{ @Override public void run(){ T obj2 = T.getinstance(); for(int i=0;i<10;i++){ System.out.println("B"+obj2.b); obj2.b++; } }}
结果
run:
A1
A2
A3
A4
A5
A6
A7
A8
A9
A10
B1
B12
B13
B14
B15
B16
B17
B18
B19
B20
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
5、静态内部类[推荐用]
实验代码:
package test;public class Test { public static void main(String[] args) { A obj1=new A(); B obj2 = new B(); obj1.start(); obj2.start(); } }class T{ public int b ; private T(){ b = 1; } private static class T1{ private static final T obj = new T(); } public static T getinstance(){ return T1.obj; }}class A extends Thread{ @Override public void run(){ T obj1= T.getinstance(); for(int i=0;i<10;i++){ System.out.println("A"+obj1.b); obj1.b++; } }}class B extends Thread{ @Override public void run(){ T obj2 = T.getinstance(); for(int i=0;i<10;i++){ System.out.println("B"+obj2.b); obj2.b++; } }}结果:
run:
A1
A2
B1
A3
B4
A5
B6
A7
B8
B10
B11
B12
A9
A14
A15
B13
A16
B17
A18
B19
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
以上就是我结合网上其他博主的博客,加上自己的实验理解总结的关于java单例在多线程的实现和安全。
- java的单例详解
- java单例详解
- JAVA单例详解
- Java单例详解
- Java单例详解
- java 单例模式详解
- Java 单例模式详解
- java 单例模式详解
- Java 单例模式详解
- Java 单例模式详解!
- JAVA单例模式详解
- Java 单例模式详解
- Java 单例模式详解
- Java 单例模式详解
- Java 单例模式详解
- java单例模式详解
- Java 单例模式详解
- Java 单例模式详解
- Android复习知识点总结(一)
- C语言中【基本文件操作】
- Mysql Binlog三种格式介绍及分析
- i++与++i的区别
- HTML+CSS实现动态背景登录页面
- java的单例详解
- 【java知识点异常】Java 异常Exception e中e的getMessage()和toString()方法的区别
- OutputStream encountered error during write
- 实战
- springmvc 的 json返回配置
- leetcode228. Summary Ranges
- Modern PHP
- 排序之冒泡排序
- OC基础-类和对象深入理解04