单例与多例的线程安全问题

单例与多例的线程安全问题


阅读之前可以先回顾一下static的主要使用情况:
1、static方法
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
2、static变量

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。
而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。
3、static代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。
在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。

下面列出线程不安全情况的代码:
实体类:

public class Entity {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

获取单例对象:

public class Single {

    private static final Entity entity = new Entity();

    public static Entity getEntity() {
        return entity;
    }
}

创建两个线程:

public class ThreadA extends Thread {
    Entity entity = Single.getEntity();

//    Entity entity = new Entity();

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            entity.setCount(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
            System.out.println("A" + entity.getCount());
//            System.out.println("A" + entity.getCount().get());
        }
    }
}
public class ThreadB extends Thread {
        Entity entity = Single.getEntity();
//    Entity entity = new Entity();

    @Override
    public void run() {
        for (Integer i = 5; i < 10; i++) {
            entity.setCount(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B" + entity.getCount());
//            System.out.println("B" + entity.getCount().get());
        }
    }
}

测试方法:

public class Test {
    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
    }
}

第一种情况:单例的非static成员变量
此时的结果可以看出线程不安全的:

B5
A5
B1
A1
A2
B2
B8
A8
A4
B4

此时将上述情况使用多例对象来执行,将ThreadA、ThreadB类中的
Entity entity = Single.getEntity();
改为 Entity entity = new Entity();
第二种情况:多例的非static成员变量:
此时输出结果是理想状态,是线程安全的,已通过多例控制线程安全:

A0
B5
A1
B6
A2
B7
A3
B8
A4
B9

接下来看一下static成员变量时,单例与多例的情况:
对实体类进行修改:

public class Entity {
    private static int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        Entity.count = count;
    }
}

第三种情况:多例的static成员变量:
执行测试代码,发现多例的static成员变量也是线程不安全的:

A5
B1
A6
B2
A7
B3
A8
B4
A9
B9

要解决这种线程不安全问题可使用ThreadLocal, 相比synchronized个人比较喜欢ThreadLocal, synchronized排队访问,ThreadLocal创建变量副本,ThreadLocal通过空间换时间。修改实体类:

public class Entity {
    private static ThreadLocal<Integer> count = new ThreadLocal<>();

    public ThreadLocal<Integer> getCount() {
        return count;
    }

    public void setCount(int count) {
        Entity.count.set(count);
    }
}

ThreadA、B类的输出改为:
System.out.println(“B” + entity.getCount().get());
测试结果:

B5
A0
A1
B6
A2
B7
B8
A3
B9
A4

第四种情况:单例的static成员变量:
最后看一下单例的static成员变量:显然也是线程不安全的,可使用ThreadLocal来解决。

总之 单例 非static成员变量及static成员变量都是线程不安全, 多例 非static成员变量线程安全,但多例static成员变量会有线程不安全情况。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供全局访问点。在多线程环境下,确保单例对象的线程安全性是很重要的。 有几种方法可以保证单例对象的线程安全: 1. 饿汉式(线程安全):在类加载时就创建单例对象。这种方式在访问量较大时可能会导致性能问题,但是在多线程环境下是线程安全的。 ```java public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } ``` 2. 懒汉式(线程安全,使用 synchronized):在首次调用时创建单例对象。通过加锁的方式保证线程安全,但每次获取实例时都需要进行同步,可能会影响性能。 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 3. 双重检查锁定(Double-checked locking)(线程安全):使用双重检查锁定机制,在实例未创建时进行同步,避免每次都进行同步。 ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 4. 静态内部类(线程安全):利用类的加载机制和静态内部类的特性,实现懒加载且线程安全单例模式。 ```java public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } } ``` 这些方法都可以保证单例对象在多线程环境下的线程安全性,根据具体的需求和性能考虑选择适合的实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值