Java单例模式中,volatile与synchronized如何有效运用在多线程环境下?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1384个文字,预计阅读时间需要6分钟。
文章目录背景多线程环境中的加载单例模式问题原因分析synchronized与volatile修饰总结背景在单例模式的设计中,通常会使用懒汉式或饿汉式来实现。但在多线程环境下,这两种方式都存在线程安全问题。本文将探讨在多线程环境中使用加载单例模式时遇到的问题,并分析原因,最后给出解决方案。
多线程环境中的加载单例模式问题在多线程环境下,当多个线程同时访问单例实例时,可能会出现多个实例被创建的情况,导致线程安全问题。
原因分析
1.线程不安全:在多线程环境下,当多个线程同时执行到单例实例的创建代码时,可能会出现多个线程同时通过if条件判断,从而创建多个实例。
2.内存可见性:当一个线程修改了共享变量的值后,其他线程可能无法立即看到这个修改,导致线程安全问题。
synchronized与volatile修饰
为了解决上述问题,可以使用synchronized和volatile关键字来保证单例实例的线程安全。1. synchronized:使用synchronized关键字可以保证同一时刻只有一个线程能够执行创建单例实例的代码。
2.volatile:使用volatile关键字可以保证变量的可见性,即当一个线程修改了共享变量的值后,其他线程能够立即看到这个修改。
总结
在多线程环境中,使用加载单例模式时,需要注意线程安全问题。通过使用synchronized和volatile关键字,可以有效地解决线程安全问题。在实际应用中,应根据具体需求选择合适的方式来实现单例模式。文章目录
- 背景
- 饿汉式加载单例模式在多线程环境中的问题
- 原因分析
- synchronized同步与volatile修饰
- 总结
背景
在单例模式的设计中,一般会用到两种方式:
立即加载 : 在类加载初始化的时候就主动创建实例;也称为饿汉式加载。
延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。也称为懒汉式加载。
//立即创建对象
private static SingletonThread singletonThread = new SingletonThread ();
private SingletonThread (){}
public static SingletonThread getInstance(){
return singletonThread ;
}
//懒汉式加载
private static SingletonThread instance;
//使用用才创建对象
public static SingletonThread getInstance() {
if (instance == null) {
instance = new SingletonThread();
}
return instance;
}
饿汉式加载单例模式在多线程环境中的问题
/*** 懒汉式单例模式在多线程安全的问题
*
* @author zhuhuix
* @date 2020-05-08
*/
public class SingletonThread {
static SingletonThread instance;
static SingletonThread getInstance() {
if (instance == null) {
instance = new SingletonThread();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonThread singletonThread = SingletonThread.getInstance();
//打印线程获取的hash值,用来判断返回的是否是同一单例
System.out.println(Thread.currentThread().getName() + ":" + singletonThread.hashCode());
}).start();
}
}
}
多次运行以上程序,会出现以下BUG,第一个线程得到的实例的hash值与其他线程的得到的hash不一致,单例模式未生效。
原因分析
- 饿汉式加载单例模式在单例类创建时就完成了对象的初始化,在多线程环境下,线程访问单例时对象就创建完成了,所以线程天生就是安全的。
- 而懒汉式加式模式是使用单例类时才完成对象的初始化,在多线程环境下,多个线程会同时进入到 if (instance == null) { instance = new SingletonThread();}很有可能造成创建出多个实例,违背了单例模式的初衷。
synchronized同步与volatile修饰
那怎么解决以上代码中有可能出现的多个实例的问题呢?可以添加synchronized同步锁与volatile修饰静态变量来解决(synchronized的原理可参见 java多线程:synchronized的深度理解)(volatile的原理可参见java多线程:volatile的深度理解 )
/*** 懒汉式单例模式在多线程安全的问题
*
* @author zhuhuix
* @date 2020-05-08
*/
public class SingletonThread {
//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个 不完整的实例
static volatile SingletonThread instance;
static SingletonThread getInstance() {
if (instance == null) {
//对代码块线程同步锁,进行双重判断
synchronized (SingletonThread.class) {
if (instance == null) {
instance = new SingletonThread();
}
}
//
}
return instance;
}
static void destroy() {
instance = null;
}
public static void main(String[] args) throws InterruptedException {
for (int k = 0; k < 100; k++) {
System.out.println("第" + (k + 1) + "次");
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
SingletonThread singletonThread = SingletonThread.getInstance();
//打印线程获取的hash值,用来判断返回的是否是同一单例
System.out.println(Thread.currentThread().getName() + ":" + singletonThread.hashCode());
});
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
for (int i = 0; i < 10; i++) {
threads[i].join();
}
SingletonThread.destroy();
}
}
}
总结
为保证程序的执行效率 ,考虑只对部分代码块用synchronized实现同步锁,并且用volatile进行修饰静态变量,这种做法无疑是优秀的。
本文共计1384个文字,预计阅读时间需要6分钟。
文章目录背景多线程环境中的加载单例模式问题原因分析synchronized与volatile修饰总结背景在单例模式的设计中,通常会使用懒汉式或饿汉式来实现。但在多线程环境下,这两种方式都存在线程安全问题。本文将探讨在多线程环境中使用加载单例模式时遇到的问题,并分析原因,最后给出解决方案。
多线程环境中的加载单例模式问题在多线程环境下,当多个线程同时访问单例实例时,可能会出现多个实例被创建的情况,导致线程安全问题。
原因分析
1.线程不安全:在多线程环境下,当多个线程同时执行到单例实例的创建代码时,可能会出现多个线程同时通过if条件判断,从而创建多个实例。
2.内存可见性:当一个线程修改了共享变量的值后,其他线程可能无法立即看到这个修改,导致线程安全问题。
synchronized与volatile修饰
为了解决上述问题,可以使用synchronized和volatile关键字来保证单例实例的线程安全。1. synchronized:使用synchronized关键字可以保证同一时刻只有一个线程能够执行创建单例实例的代码。
2.volatile:使用volatile关键字可以保证变量的可见性,即当一个线程修改了共享变量的值后,其他线程能够立即看到这个修改。
总结
在多线程环境中,使用加载单例模式时,需要注意线程安全问题。通过使用synchronized和volatile关键字,可以有效地解决线程安全问题。在实际应用中,应根据具体需求选择合适的方式来实现单例模式。文章目录
- 背景
- 饿汉式加载单例模式在多线程环境中的问题
- 原因分析
- synchronized同步与volatile修饰
- 总结
背景
在单例模式的设计中,一般会用到两种方式:
立即加载 : 在类加载初始化的时候就主动创建实例;也称为饿汉式加载。
延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。也称为懒汉式加载。
//立即创建对象
private static SingletonThread singletonThread = new SingletonThread ();
private SingletonThread (){}
public static SingletonThread getInstance(){
return singletonThread ;
}
//懒汉式加载
private static SingletonThread instance;
//使用用才创建对象
public static SingletonThread getInstance() {
if (instance == null) {
instance = new SingletonThread();
}
return instance;
}
饿汉式加载单例模式在多线程环境中的问题
/*** 懒汉式单例模式在多线程安全的问题
*
* @author zhuhuix
* @date 2020-05-08
*/
public class SingletonThread {
static SingletonThread instance;
static SingletonThread getInstance() {
if (instance == null) {
instance = new SingletonThread();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonThread singletonThread = SingletonThread.getInstance();
//打印线程获取的hash值,用来判断返回的是否是同一单例
System.out.println(Thread.currentThread().getName() + ":" + singletonThread.hashCode());
}).start();
}
}
}
多次运行以上程序,会出现以下BUG,第一个线程得到的实例的hash值与其他线程的得到的hash不一致,单例模式未生效。
原因分析
- 饿汉式加载单例模式在单例类创建时就完成了对象的初始化,在多线程环境下,线程访问单例时对象就创建完成了,所以线程天生就是安全的。
- 而懒汉式加式模式是使用单例类时才完成对象的初始化,在多线程环境下,多个线程会同时进入到 if (instance == null) { instance = new SingletonThread();}很有可能造成创建出多个实例,违背了单例模式的初衷。
synchronized同步与volatile修饰
那怎么解决以上代码中有可能出现的多个实例的问题呢?可以添加synchronized同步锁与volatile修饰静态变量来解决(synchronized的原理可参见 java多线程:synchronized的深度理解)(volatile的原理可参见java多线程:volatile的深度理解 )
/*** 懒汉式单例模式在多线程安全的问题
*
* @author zhuhuix
* @date 2020-05-08
*/
public class SingletonThread {
//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个 不完整的实例
static volatile SingletonThread instance;
static SingletonThread getInstance() {
if (instance == null) {
//对代码块线程同步锁,进行双重判断
synchronized (SingletonThread.class) {
if (instance == null) {
instance = new SingletonThread();
}
}
//
}
return instance;
}
static void destroy() {
instance = null;
}
public static void main(String[] args) throws InterruptedException {
for (int k = 0; k < 100; k++) {
System.out.println("第" + (k + 1) + "次");
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
SingletonThread singletonThread = SingletonThread.getInstance();
//打印线程获取的hash值,用来判断返回的是否是同一单例
System.out.println(Thread.currentThread().getName() + ":" + singletonThread.hashCode());
});
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
for (int i = 0; i < 10; i++) {
threads[i].join();
}
SingletonThread.destroy();
}
}
}
总结
为保证程序的执行效率 ,考虑只对部分代码块用synchronized实现同步锁,并且用volatile进行修饰静态变量,这种做法无疑是优秀的。

