单例与多例的线程安全问题
阅读之前可以先回顾一下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成员变量会有线程不安全情况。