最近在阅读 《Inside the JVM》 这本书,结合一些日常工作学习中的感想,随便写一些东西,蜻蜓点水,不必有章法。
关于“单例同步”:
一直有人在问单例对象的并发调用是否需要同步,基本属于“月经帖”了,答案是现成的满天下都是,但真正能让人心里踏实下来的解释寥寥无几。实际上,只要学习了一些JVM的运行原理,解释这个问题就不难了。
如果一个类是单例的,比如某些DAO的设计,那么所有的线程来访问这个类的实例的时候,它们获得的都将是同一个对象,这是不言自明的。如果这些线程的当前操作是“互斥”的,那么每个线程就必须在取得该实例的访问资格的时候为该对象上锁,以独享该对象直到当前操作结束,以免在操作中途被其它线程介入而产生不可预知的结果。问题是,什么样的操作是“互斥”的呢?
简单地说,互斥操作就是两个操作企图对它俩共享的某个资源进行修改,而修改的结果是不可预知的。于是问题就变成了,什么才是“共享的资源”?从纯粹 java语法的角度这个问题没法解释,因为它遵循的是当前java虚拟机的规范描述。现假设两个线程正企图同时访问一个单例对象的方法,如,
int method1(int i) {
int j = 3;
return i+j;
}
一个规范的虚拟机线程在调用method1()的时候是这样做的:
1) 把method1()的局部变量,包括参数,压入当前线程的栈;
2) 从当前线程栈弹出变量j,并赋予数值3;
3) 从当前线程栈弹出参数i,与j执行加法运算;
4) 从当前线程栈中释放当前方法占用的栈帧,并把method1()的结果压入当前线程栈。
需要说明的是,当前线程栈是当前线程独有的,绝对不会被其它线程访问到。这样,只要你在method1()里面使用的全都是局部变量或参数,那就不需要为多线程的并发调用发愁,因为每个线程都有自己的栈帧,各不相干。
复杂一点,如果method1()是这样定义的:
int method1(int i, SingletonClass singleObj) {
singleObj.intValue ++;
int j = i + singleObj.intValue;
return j;
}
这下我们就不得不考虑线程同步问题了,这个方法显然包含了一个互斥的操作“singleObj.intValue ++;”。
前面说过,方法的参数会被压入当前线程私有的栈直到方法结束,但这里要注意的是,singleObj只是一个引用地址而非真正的对象实例,因此,尽管
singleObj这个引用值是被压入线程私有栈去的,但真正的对象实例却是在堆里存放的,栈虽然是线程私有的,堆却是所有线程共享的,因此
singleObj的成员变量intValue是完全有可能在当前线程执行第二行代码前被其它线程修改了的。比如说,线程1调用mothod1()的时候
singleObj.intValue的值是1,
i的值是2,那么正确的情况下,method1()的返回值应该是4。但当线程1和线程2几乎同时调用method1(),线程2恰好在线程1把
intValue变成2之后的一瞬间又执行了一次singleObj.intValue
++,由于singleObj是单例,两个线程遇到的singleObj是同一个对象,因此这次运算将把intValue变成3。接下来线程1继续第二行
代码,结果j的结果变成了i+3 = 2+3 = 5 。 如此一来,线程1调用method1()的返回结果究竟会是 4 还是 5
是无法确定的,只能凭运气,寄望线程2在线程1从调用method1()到取得返回值之间的这段时间打盹。在绝大多数情况下,这种“凭运气”的做法是不能
接受的,我们需要向线程1保证,在它调用method1()期间绝不会收到线程2的干扰。做法如下:
int method1(int i, SingletonClass singleObj) {
int j = 0;
synchronize(singleObj) {
singleObj.intValue ++;
j = i + singleObj.intValue;
}
return j;
}
这个写法仍然有缺陷,因为线程2很可能在线程1执行int j = 0 的时候修改singleObj的intValue,所以比较可靠的应该在调用method1()之前锁住singleObj:
synchronize(singleObj) {
int result = obj.method1(2, singleObj);
}
小小总结一下,“一个方法如果涉及对某个共享对象(或堆对象)的写操作,那么它必须同步该对象”这个说法在大多数情况下都对,但还有些失之笼统,或许这样说比较准确些,“如果一个方法对某共享对象的写操作会造成其它线程返回值的不确定性,则该方法应该同步该对象。”
分享到:
相关推荐
3. **跨JVM环境**:在集群环境中或者远程服务调用时,每个JVM都有自己的独立内存空间,因此每个JVM中都可能存在一个单例实例。解决此问题需要通过外部机制(如分布式缓存)来协调多个JVM中的单例实例。 4. **单例...
除了构造函数,还可以通过静态工厂方法来获取单例对象,这种方法可以提供更大的灵活性,例如改变返回的对象类型,而无需更改调用代码。同时,静态工厂方法不会暴露构造函数,使得类更难以被其他方式实例化,增加了...
单例模式是设计模式中的一种,它在Java编程中被广泛应用,特别是在需要全局共享资源或者控制实例数量的情况下。单例模式的基本思想是确保一个类在任何情况下都只有一个实例,并提供一个全局访问点来获取这个唯一的...
通过在`getInstance()`方法上添加`synchronized`关键字,确保了线程安全,但在多线程环境下会降低性能,因为每次调用都需要同步,即使实例已经创建。 3. **饿汉式**: `Singleton`实例在类加载时立即创建。这种...
**正文** Java单例模式是一种常用的软件设计模式...需要注意的是,Java 5之后的版本,由于JVM对volatile的优化,DCL式已经能够很好地解决线程安全问题。在Java的并发编程中,正确理解和运用单例模式是非常重要的技能。
在多线程和并发环境中,单例模式的实现需要特别注意线程安全的问题。 单例模式的实现方式通常有以下几种: 1. 懒汉式:这种实现方式是在第一次调用获取实例的方法时才初始化实例。为了保证线程安全,可以使用双重...
总的来说,开发高性能、高并发的Java Web应用需要从多个层面考虑,包括代码编写、内存管理、并发控制、系统配置等多个方面,通过综合运用各种优化手段,可以构建出响应迅速、资源利用率高的应用程序。
关于Java中单例模式的争议,例如单例对象是否会被垃圾回收,实际上取决于JVM的实现和对象的可达性。在HotSpot虚拟机1.6及更高版本中,只要静态引用仍然存在,单例对象就不会被垃圾回收。另外,使用不同类加载器加载...
4. **多线程**:线程同步、并发工具类、ExecutorService等。 5. **IO流**:文件操作、网络通信、数据流的读写。 6. **反射**:动态调用方法、创建对象、获取类信息等。 7. **设计模式**:实现常见的设计模式,如单例...
`volatile`是Java虚拟机(JVM)提供的一种轻量级的同步机制,主要用于解决多线程环境中的可见性问题。其核心功能包括保证可见性、不保证原子性以及禁止指令重排序。 **1.2 `volatile`的三大特性** - **可见性**: 当一...
Java多线程和并发知识是Java开发...以上内容涵盖了Java多线程和并发编程的主要知识点,从理论到实践,从基础到高级,全面解析了并发编程的核心概念和工具。掌握这些知识,开发者可以编写出高效、可靠的多线程应用程序。
面试中可能需要解释如何处理事务的回滚、提交以及并发事务间的隔离级别。 最后,计算机网络部分,面试者可能需要了解TCP/IP协议、HTTP协议、网络层次结构(如OSI七层模型或TCP/IP四层模型)、Socket编程等基础知识...
在Java面试中,掌握核心知识点是至关重要的,其中包括对JVM(Java虚拟机)的理解。JVM是Java程序运行的基础,它负责解析.class文件,执行字节码,并提供了内存管理、垃圾回收等机制。理解JVM的工作原理对于优化程序...
synchronized可以作用于方法或代码块,锁对象不同,如实例方法锁定的是实例对象,静态方法锁定的是类对象,同步代码块则可以自定义锁定对象。 5. **锁的实现机制**: - **Java对象头**:锁的信息存储在Java对象头...
手动触发GC可以通过`System.gc()`,但不推荐,因为GC策略由JVM管理,频繁调用可能导致性能下降。 设计模式是解决软件设计中常见问题的模板,如单例模式确保类只有一个实例,可使用双重检查锁定实现线程安全的单例。...
3. 有助于控制并发,对于需要跨线程共享的对象,单例模式能保证所有线程共享同一实例。 单例模式的实现方式有很多种,其中常见的包括: 1. 饿汉式:在类加载时就初始化实例,线程安全,但无法延迟加载。Java中可以...
每种方式都有其适用场景和优缺点,选择哪种方式取决于具体的需求,例如是否需要延迟初始化、是否关心性能、以及是否考虑JVM版本等因素。在实际开发中,应根据项目需求和性能测试结果来选择最合适的单例实现方式。
Actors是轻量级的线程,通过消息传递进行通信,降低了并发编程中的同步问题,提高了系统的可扩展性和性能。 4. **领域特定语言(DSLs)** Scala鼓励创建内部DSL,使得业务逻辑可以以接近自然语言的方式表达,提高...