`

java 单例加锁方法的讨论

 
阅读更多

//2014.8.26  review

一个经典 DCL 问题

 

    public class Singleton {  
    private static Singleton instance=null;  
    public static Singleton getInstance()  
    {  
      if (instance == null)  
      {  
        synchronized(Singleton.class) {  //1  
          if (instance == null)          //2  
            instance = new Singleton();  //3  
        }  
      }  
      return instance;  
    }  
    }  

 

 

这段代码有问题么?标准的double check.instance = new Singleton()根据以上几点分析,
可能执行执行了下列伪代码:

  1. mem = allocate();             //Allocate memory for Singleton object.  
  2. instance = mem;               //Note that instance is now non-null, but  
  3.                               //has not been initialized.  
  4. ctorSingleton(instance);      //Invoke constructor for Singleton passing  
  5.                               //instance. 

 

有可能线程B在线程A完成对象的初始化之前就可能得到了对象的引用。

当线程A执行到instance = mem时,线程B 正好执行到外部的instance == null,此时,这个引用已经不为null,但是这个statnce还没有构造完成,线程B的操作立即返回使用该instance,这是不安全的。这是从操作次序被重新排序得到的分析结果,从另外happen-before的角度来看,这里多个线程操作共享变量instance之间并没有明显的happen-before关系,因此多个线程对instanc的读写可能发生不可见的情况。instance变量申明为volatile即可,既保证了可见性,又保证了操作不会被排序。然而,使用volatile来实现毕竟有性能损耗,因此如果要实现单例,完全可以避免使用DCL,而采用static方式。

例如:要解决上面提到的问题,则

    public class Singleton {    
    private static class Singleton Holder{    
    private static Singleton instance = new Singleton ();    
    }    
    public static Singleton getInstance(){    
    return SingletonHolder.instance ;    
    }    
    }   

 这种实现方式既保证了足够的惰性,又避免了同步或者保持可见性带来的性能损耗。

引用自http://blog.csdn.net/lovingprince/article/details/5450246

 

//2012.8.3

java 单例加锁方法:

ScheduleEngine是个单例类,在获得实例的方法getinstance中,两次判断其是否为空,有利于多线程的并发操作。

使得实例化时,只在第一次加锁,这样效率会有提高。

 

class ScheduleEngine{
	
		private static Lock instanceLock=new ReentrantLock();
		
		private ScheduleEngine() {
			setproperties;
		}
		
		public static ScheduleEngine getInstance(int temphandlerType) {
			if(null==engine) {
				instanceLock.lock();
				try
				{
				if(null==engine)
				{
				handlerType=temphandlerType;
				engine=new ScheduleEngine(temphandlerType);
				}
		
				}
				finally
				{
				instanceLock.unlock();
				}
			}
			return engine;
			} 
	}

 

初始实例化 单例c3p0对象的方法,常用的是

 

public final class ConnectionManager {

	private static ConnectionManager instance;
	private static ComboPooledDataSource cpds;

	private static String c3p0Properties;

	/**
	 * 从数据库连接池取连接
	 * @throws Exception
	 */
	private ConnectionManager() throws Exception {
		Properties p = new Properties();
		c3p0Properties = System.getProperty("user.dir") +
				"/mom_project_config/database.properties";
//		p.load(this.getClass().getClassLoader().getResourceAsStream(c3p0Properties));
		p.load(new BufferedInputStream(new FileInputStream(c3p0Properties)));
//		String url = p.getProperty("url") + p.getProperty("database");
		String url = p.getProperty("url") + p.getProperty("database")+"?useUnicode=true&characterEncoding=UTF-8";
		cpds = new ComboPooledDataSource();
		cpds.setDriverClass(p.getProperty("driverclass"));
		cpds.setJdbcUrl(url);
		cpds.setUser(p.getProperty("user"));
		cpds.setPassword(p.getProperty("password"));
		// 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 acquireIncrement
		cpds.setAcquireIncrement(Integer.valueOf(p
				.getProperty("acquireincrement")));
		// 定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 acquireRetryAttempts
		cpds.setAcquireRetryAttempts(Integer.valueOf(p
				.getProperty("acquireretryattempts")));
		// 两次连接中间隔时间,单位毫秒。Default: 1000 acquireRetryDelay
		cpds.setAcquireRetryDelay(Integer.valueOf(p
				.getProperty("acquireretrydelay")));
		// 自动提交事务;连接关闭时默认将所有未提交的操作回滚。Default: false autoCommitOnClose
		cpds.setAutoCommitOnClose(Boolean.valueOf(p
				.getProperty("autocommitonclose")));
		// 当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,
		// 如设为0则无限期等待.单位毫秒,默认为0
		cpds.setCheckoutTimeout(Integer.valueOf(p
				.getProperty("checkouttimeout")));
		// 每多少秒检查所有连接池中的空闲连接。默认为0表示不检查。Default: 0 idleConnectionTestPeriod
		cpds.setIdleConnectionTestPeriod(Integer.valueOf(p
				.getProperty("idleconnectiontestperiod")));
		// 最大空闲时间,25000秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 maxIdleTime
		cpds.setMaxIdleTime(Integer.valueOf(p.getProperty("maxidletime")));
		// 初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 initialPoolSize
		cpds.setInitialPoolSize(Integer.valueOf(p
				.getProperty("initialpoolsize")));
		// 连接池中保留的最小连接数。
		cpds.setMinPoolSize(Integer.valueOf(p.getProperty("minpoolsize")));
		// 连接池中保留的最大连接数。Default: 15 maxPoolSize
		cpds.setMaxPoolSize(Integer.valueOf(p.getProperty("maxpoolsize")));
		// JDBC的标准参数,用以控制数据源内加载的PreparedStatement数据.但由于预缓存的Statement属于单个Connection而不是整个连接池.所以
		// 设置这个参数需要考滤到多方面的因素,如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭.默认为0;
		cpds.setMaxStatements(Integer.valueOf(p.getProperty("maxstatements")));
		// 连接池内单个连接所拥有的最大缓存被关闭.默认为0;
		cpds.setMaxStatementsPerConnection(Integer.valueOf(p
				.getProperty("maxstatementsperconnection")));
		// C3P0是异步操作的,缓慢的JDBC操作通过帮助进程完成.扩展这些操作可以有效的提升性能,通过多数程实现多个操作同时被执行.默为为3
		cpds.setNumHelperThreads(Integer.valueOf(p
				.getProperty("numhelperthreads")));
		// 用户修改系统配置参数执行前最多等待的秒数.默认为300;
		cpds.setPropertyCycle(Integer.valueOf(p.getProperty("propertycycle")));
		// 如果设为true那么在取得连接的同时将校验连接的有效性。Default: false testConnectionOnCheckin
		cpds.setTestConnectionOnCheckin(Boolean.valueOf(p
				.getProperty("testconnectiononcheckin")));
		// 因性能消耗大请只在需要的时候使用它。
		// 如果设为true那么在每个connection提交的时候都将校验其有效性。
		// 建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default:
		// false testConnectionOnCheckout
		cpds.setTestConnectionOnCheckout(Boolean.valueOf(p
				.getProperty("testconnectionOncheckout")));
		// 获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。
		// 但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。
		// 如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false
		// breakAfterAcquireFailure
		cpds.setBreakAfterAcquireFailure(Boolean.valueOf(p
				.getProperty("breakafteracquirefailure")));

	}

	/**
	 * 获得ConnectionManager单例对象
	 * @return
	 */
	public synchronized static ConnectionManager getInstance() {
		if (instance == null) {
			try {
				instance = new ConnectionManager();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return instance;
	}

	/**
	 * 获得连接
	 * @return
	 */
	public Connection getContection() {
		try {
			return cpds.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

 

在初始化获得单例的方法上面加锁,不利于并发操作的执行,用第一段代码两次判断是否为空的方式,可以减少代码执行中锁的引用。 不足之处烦请朋友们指正。。

 

 

 

 

2
10
分享到:
评论
9 楼 rainsilence 2012-08-05  
J2EE大鸟 写道
rainsilence 写道
想说的1F2F都说了

感觉你写的这篇文章很实在  http://rainsilence.iteye.com/blog/1478455

呵呵。像你这样能把我文章看过一遍的人已经很少了。现在人大多比较浮躁。。字一多就看不下去。。
8 楼 J2EE大鸟 2012-08-03  
feng_jyie 写道
jdk 1.5 1.6 版本的双检锁机制也是可以的,不过变量前要添加  volatile 关键字

好的,完了我试下。。
7 楼 J2EE大鸟 2012-08-03  
rainsilence 写道
想说的1F2F都说了

感觉你写的这篇文章很实在  http://rainsilence.iteye.com/blog/1478455
6 楼 rainsilence 2012-08-03  
想说的1F2F都说了
5 楼 feng_jyie 2012-08-03  
jdk 1.5 1.6 版本的双检锁机制也是可以的,不过变量前要添加  volatile 关键字
4 楼 J2EE大鸟 2012-08-03  
lovexp2010 写道
Lazy initialization holder class:

public class Singleton {

	private Singleton() {
	}
	
	private static class SingletonHolder {
		static final Singleton instance = new Singleton();
	}

	public static Singleton getInstance(){
		return SingletonHolder.instance;
	}
	
}


SingletonHolder 直到使用时JVM才加载……


我有必要去了解一下JVM的机制了,以前只要是把功能实现就好,多谢大哥。。
3 楼 J2EE大鸟 2012-08-03  
learnworld 写道
双重检查锁定是不安全的,http://www.ibm.com/developerworks/cn/java/j-dcl.html

为避免单例中代价高昂的同步,程序员非常聪明地发明了双重检查锁定习语。不幸的是,鉴于当前的内存模型的原因,该习语尚未得到广泛使用,就明显成为了一种不安全的编程结构。重定义脆弱的内存模型这一领域的工作正在进行中。尽管如此,即使是在新提议的内存模型中,双重检查锁定也是无效的。对此问题最佳的解决方案是接受同步或者使用一个 static field。
这可能也是我平时没有注意到的。。谢谢大哥。。
2 楼 lovexp2010 2012-08-03  
Lazy initialization holder class:

public class Singleton {

	private Singleton() {
	}
	
	private static class SingletonHolder {
		static final Singleton instance = new Singleton();
	}

	public static Singleton getInstance(){
		return SingletonHolder.instance;
	}
	
}


SingletonHolder 直到使用时JVM才加载……
1 楼 learnworld 2012-08-03  
双重检查锁定是不安全的,http://www.ibm.com/developerworks/cn/java/j-dcl.html

相关推荐

    Java双重检查加锁单例模式的详解

    Java双重检查加锁单例模式是一种常用的单例模式实现方法,但是在多线程环境下,它存在一些问题。在这篇文章中,我们将探讨Java双重检查加锁单例模式的详解,包括它的优点和缺点,以及如何正确地使用它。 Java双重...

    使用Java单例模式实现一个简单的日志记录器.txt

    本文介绍了一种使用Java单例模式实现简单日志记录器的方法。通过定义静态变量、私有构造函数以及线程安全的实例获取方法,实现了日志记录器的单例模式。此外,还提供了写入日志的方法,便于实际应用中的日志管理。...

    Java 单例模式 工具类

    Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...

    Java单例模式设计

    Java单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供全局访问点。这种模式在需要频繁创建和销毁对象的场景中,或者当对象昂贵时(如数据库连接),能够节省系统资源,提高效率。本篇文章将深入探讨...

    Java 单例模式 懒汉模式

    Java 单例模式 懒汉模式 //懒汉式 多线程中不可以保证是一个对象

    JAVA Lock加锁实例

    (以下场景皆为单例模式下运行) lock.lock()的加锁方式,会使后续请求的线程堵塞等待。(方案A) lock.tryLock()的加锁方式,不会堵塞,会立即返回加锁成功与否。(方案AEX) lock.tryLock(1000, TimeUnit.SECONDS)...

    Java单例模式与工厂模式简单示例代码

    在Java中,单例模式的实现通常有几种方法: 1. **饿汉式(静态常量)**:在类加载时就完成初始化,所以类加载比较慢,但获取对象的速度快,且线程安全。 ```java public class Singleton { private static final ...

    Java SE程序 单例模式

    Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式...

    Java实现多种单例模式

    在Java编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景中尤其有用,因为它可以节省系统资源并确保对象间的协调一致。以下是...

    java单例模式代码实例

    2. 避免序列化破坏单例:如果单例类实现了Serializable接口,应重写readResolve()方法以保持单例。 总结,Java单例模式在软件设计中有着广泛应用,理解并合理运用不同的实现方式,可以帮助我们更好地控制对象的生命...

    Java单例模式实现静态内部类方法示例

    "Java单例模式实现静态内部类方法示例" Java单例模式是软件设计模式中最基本和最常见的一种设计模式,也是最容易理解的一种设计模式。它的主要思想是确保某个类只有一个实例,并且提供一个全局访问点来访问该实例。...

    Java 单例模式线程安全问题

    Java 单例模式线程安全问题详解 Java 单例模式线程安全问题是指在 Java 中实现单例模式时,如何确保线程安全的问题。单例模式是指在整个应用程序生命周期中,只有一个实例存在的设计模式。这种模式可以提高性能,...

    快速理解java单例的小程序

    在Java中,通常有几种实现单例的方法: 1. 饿汉式(静态常量):在类加载时就完成初始化,所以没有线程安全问题。这种方式简单且高效,但可能导致不必要的内存浪费。 ```java public class Singleton { private ...

    java单例模式完全讲解.pdf

    非常详细的Java单例模式讲解的文档,请求通过,已经上传过一次了

    java单例模式完全剖析

    Java中的单例模式是一种常用的设计模式,用于控制类的实例化过程,确保在整个应用程序中,一个类只有一个实例存在。这种模式常用于系统资源管理,如数据库连接池、缓存管理和线程池等场景。 单例模式的核心在于限制...

    JAVA单例模式_1548149729.doc

    JAVA单例模式_1548149729

    java单例模式课程.pdf

    在Java中实现单例模式有多种方法,包括: 1. 饿汉式(Eager Initialization):在类加载时就初始化,确保单例的唯一性。这种方式线程安全,但可能会造成资源浪费,因为即使未使用,也会提前初始化。 ```java ...

    java单例设计模式

    Java单例设计模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置信息等,以提高效率并减少系统内存开销。这个...

    java单例模式详解Java系列2021.pdf

    Java语言中的单例模式实现有多种方式,包括饿汉式、懒汉式和登记式单例。不同的实现方式适应不同的场景和需求,各有优劣。 首先,饿汉式单例是类加载时就会立即初始化,它的好处是实现简单,且在多线程环境下能保证...

Global site tag (gtag.js) - Google Analytics