`

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

相关推荐

    C++实现的仓库入口多层次安全防御系统,涵盖认证、防火墙和入侵检测

    内容概要:本文介绍了一个用C++实现的综合性的仓库入口安全防御系统,主要包括四个模块:用户认证系统(AuthenticationSystem)、防火墙系统(FirewallSystem)、入侵检测系统(IntrusionDetectionSystem)以及整体管理的仓库安全系统(WarehouseSecuritySystem)。每个模块都承担了各自的重要职责来确保整个系统免受非法访问与潜在风险。具体来说,用户认证部分实现了账户管理及其密码保护策略,并引入了多次连续登陆失败即锁定的功能以防范暴力破解行为。防火墙系统则能够设定规则,筛选出不合法或者可疑的数据流量从而进行阻拦,同时也可以针对指定的目标设备或地址设置限制条件;入侵检测系统记录每一次进出情况并对那些带有预定义危险信号的行为发出警告提示。最后仓库安全管理部分整合了前两者,并负责全面协调各项安全机制的运作。 适合人群:具备C++编程能力的专业安全工程师或者有一定网络攻防理论背景的研发和技术管理人员。 使用场景及目标:本项目特别适用于有高网络安全防护需求的企业和个人服务器管理者,特别是对黑客入侵可能性较高的行业比如金融、政府等领域而言

    屏幕PCB LAYOUT

    屏幕PCB LAYOUT

    netCDF4-1.6.4-cp311-cp311-win32.whl.zip

    netCDF4-1.6.4-cp311-cp311-win32.whl.zip

    turbowarp下载

    turbowarp下载

    CH341编程器软件NeoProgrammer

    CH341编程器软件NeoProgrammer

    Delphi 12.3控件之QRCodeScaner.zip

    QRCodeScaner.zip

    netCDF4-1.6.2-cp38-cp38-win32.whl.zip

    netCDF4-1.6.2-cp38-cp38-win32.whl.zip

    上市公司-企业超额商誉数据(2005-2023年).zip

    上市公司-企业超额商誉数据(2005-2023年)

    IATF16949汽车认证方案获得并保持IATF认可的规则 第六版.pdf

    IATF16949汽车认证方案获得并保持IATF认可的规则 第六版.pdf

    Notepad_202503231401_51092.png

    Notepad_202503231401_51092.png

    智慧能源集团数字化管理平台项目建议书.docx

    智慧能源集团数字化管理平台项目建议书.docx

    股票相关的时间序列数据

    循环神经网络可应用于处理时间序列的数据。本人提供了一份与股票相关的时间序列数据,包含股票的开盘数据,关盘数据、最高点数据、最低点数据。供大家学习训练时使用

    jdk-8u181 windows和linux安装包

    jdk-8u181 windows和linux安装包

    C++实现的基本仓库入口安全防护系统-涵盖用户认证与防火墙机制

    内容概要:本文档展示了用C++编写的简单仓库入口安全防护系统的实现案例,详细介绍了系统的组成模块:用户认证系统利用SHA-256加密方式保护用户登录信息的安全;防火墙系统负责拦截来自特定危险区域(如某些指定IP地址范围或非法端口号)的数据流;二者共同协作以确保只有经过授权并且来源可靠的设备才被允许进入仓库区域。此外,文中还提供了完整的代码清单及其运行环境搭建指南,帮助开发者快速理解并实践这套安全解决方案的核心思路。 适合人群:对于有一定C++编程基础的学习者或开发人员来说尤为合适,他们可以借鉴此实例深化对软件安全防护机制的理解,并学会应用这些知识点来构建类似的应用场景。 使用场景及目标:该系统主要用于模拟真实的仓储设施出入口处的身份查验及网络接入管理,能够为学员提供直观的学习范本。它可以帮助使用者掌握如何整合不同的安全元素形成有效的防护措施,同时培养解决问题的能力以及团队合作精神。 其他说明:本实例仅供参考和练习目的,真实环境下建议采用更为专业可靠的产品和服务进行部署。由于采用了OpenSSL提供的哈希函数接口来进行敏感数据的加解密操作,所以项目实施前请务必确认已正确配置好相关依赖库。

    基于SpringBoot的笔记记录分享网站(源码+数据库+万字文档)376

    笔记记录分享网站,系统包含两种角色:管理员、用户,系统分为前台和后台两大模块,主要功能如下。 前台: - 首页:展示网站的基本信息、热门笔记、公告等内容。 - 用户:展示注册用户的个人信息和活跃度等。 - 公告信息:展示网站发布的最新公告和通知。 - 笔记广场:用户可以在此发布和分享自己的笔记记录。 - 商品:展示网站相关的商品信息。 - 个人中心:用户可以管理个人信息、查看自己的笔记记录等。 - 购物车:用户可以将感兴趣的商品加入购物车。 后台: - 管理员个人中心:管理员可以管理个人信息,包括修改密码、查看个人信息等。 - 管理员管理:管理员可以管理其他管理员账号,包括添加、删除、编辑等操作。 - 基础数据管理:管理员可以管理系统的基础数据,如网站配置、分类信息等。 - 笔记广场管理:管理员可以管理笔记广场的内容,包括审核、删除违规内容等操作。 - 公告信息管理:管理员可以管理网站发布的公告信息,包括添加、删除、编辑等操作。 - 日常任务管理:管理员可以管理网站的日常任务,如定时备份、数据清理等。 - 商品管理:管理员可以管理网站的商品信息,包括上架、下架、编辑商品信息等操作。

    基于vue3+js开发的个人网页,用于设计师或者开发者展示作品的响应式网页模板 .zip

    基于vue3+js开发的个人网页,用于设计师或者开发者展示作品的响应式网页模板。.zip

    te,dl,快递费空间都死了

    te,dl,快递费空间都死了

    注塑磨具分拣机20161207.smbp

    注塑磨具分拣机20161207.smbp

    最新更新!实证研究Stata代码命令汇总

    ## 一、引言 Stata是一款常用于社会科学、经济学等领域的统计软件,提供了丰富的实证分析命令。在数据管理方面,常用命令如use(加载数据)、gen(生成变量)、drop(删除变量)等;描述性统计可通过summary、tabulate和histogram等命令来进行;回归分析则依赖于regress(线性回归)、logit和probit(二项选择回归)等;假设检验可以通过ttest(t检验)和anova(方差分析)完成;面板数据分析则使用xtset和xtreg命令;此外,Stata还提供了多种可视化命令,如scatter和twoway等,帮助用户生成各种图表进行结果展示 为了帮助研究人员更好地利用Stata,马克团队整理了一套Stata实证命令汇总,覆盖了从数据的初步处理到高级统计分析的各个环节 ## 二、目录 一、数据导入和管理 (1)数据导入 (2)数据导出 二、数据的处理 (1)生成新变量 (2)格式转换 (3)缺失数据 (4)异常数据 (5)重命名变量 (6)编码分类变量 (7)设定面板数据 (8)数据合并 (9) 数据追加 三、描述性统计 (1)基本统计 (2)变量的详细统计 (3)变量的频率表 (4)变量间的相关性 (5)回归分析及其描述性统计 (6)简单统计 四、相关性分析 (1)绘制直方图 (2)绘制散点图 (3)矩阵散点图 (4)相关图 (5)回归拟合图 (6)相关系数 (7)相关系数矩阵 五、实证模型 (1)单变量分析 (2)OLS回归 (3)分位数回归 (4)Probit模型 (5)Logit模型 (6)Tobit模型 六、内生性解决 (1)工具变量法 (2)固定效应模型 (3)随机效应模型 (4)系统GMM模型 (5)DID模型 (6)PSM模型 (7)滞后期模型 七、检验分析 (1)豪斯曼检验 (2)Heckman两阶段检验 (3)调节效应检验 (4)中介效应检验 八、结果导出 (1)导出描述性统计 (2)导出相关系数 (3)导出回归结果 ## 三、概览

    Delphi 12.3控件之GenFsBinary.zip

    GenFsBinary.zip

Global site tag (gtag.js) - Google Analytics