又周五了,时间过得好快,住在集体宿舍,几个宅男共处一室好是无聊,习惯性来到CSDN。今天一个应届生同事突然问我为什么老大要求我们不要在Service里写成员变量,说不安全,说为什么不安全让他自己去了解,看上去他没有找到头绪很是痛苦,想想当初这个问题也困扰过自己,向他解说的过程,顺便写下来和大家一起探讨。老大说的好像对好像又不对,为什么这样说呢,因为我们项目的Service都是通过Spring去扫描,自然默认的都是单例,所以就有了线程安全问题。不在Service写成员变量很简单的就避免了线程不安全的情况,但一定要写成员变量怎么进行线程安全控制呢?这里说的成员变量为非static, static成员变量后面另说。一是把Sevice在spring中配置成多例,从某种情况下避免了线程不安全的情况,还有一种方式是自写代码进行线程安全控制。下面例出可能产生线程不安全的代码:
实体
public class Entity {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
实体单例
public class Single {
public static final Entity entity = new Entity();
public static Entity getEntity() {
return entity;
}
}
创建两个线程
public class ThreadA extends Thread {
Entity entity = Single.getEntity();
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());
}
}
}
public class ThreadB extends Thread {
Entity entity = Single.getEntity();
public void run() {
for (int i = 5; i < 10; i++) {
entity.setCount(i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B" + entity.getCount());
}
}
}
入口
public class Test {
public static void main(String[] args) throws Exception {
new ThreadA().start();
new ThreadB().start();
}
}
可能产生的一种输出结果是:
B0
B6
A7
B1
B8
A9
B2
A2
A3
A4
回头看我们的两个线程中的for偱环,可能错误的认为A线程只会产生A0 A1 A2 A3 A4 , B线程只会产生B5 B6 B7 B8 B9, 但很显然我们的输出中出现了 B0 ,A7等你意料之外的数据,这里就产生了线程不安全情况。我们把单例修改成多例试试,其它代码不变,只修改线程 A B的第一行代码:
public class ThreadA extends Thread {
Entity entity = new Entity();
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());
}
}
}
public class ThreadB extends Thread {
Entity entity = new Entity();
public void run() {
for (int i = 5; i < 10; i++) {
entity.setCount(i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B" + entity.getCount());
}
}
}
B5
A0
B6
B7
A1
B8
B9
A2
A3
A4
数据正确,已通过多例控制线程安全,这个仅是在非static成员变量的情况,在多例情况下如果实体中的成员变量为static,修改实体代码
public class Entity {
private static int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
B5
A6
B1
B7
A8
B2
B9
A9
A3
A4
又出现了A6等意外数据,要解决这种线程不安全问题可使用ThreadLocal, 相比synchronized个人比较喜欢ThreadLocal, synchronized排队访问,ThreadLocal创建变更副本,ThreadLocal通过空间换时间。修改实体
public class Entity {
private static ThreadLocal count = new ThreadLocal();
public int getCount() {
return count.get();
}
public void setCount(int count) {
this.count.set(count);
}
}
B5
A0
B6
B7
A1
B8
B9
A2
A3
A4
已线程安全化。(多例 static成员变量)
前面例子说到单例 非static成员变量的情况也会出现线程不安全,我们把线程A B第一行代码再次修改成
Entity entity = Single.getEntity();
private ThreadLocal count = new ThreadLocal();
运行如下:
B5
A0
B6
B7
A1
B8
B9
A2
A3
A4
已完成线程安全化。 单例 static成员变量也是线程不安全,一样可以通过ThreadLocal线程安全化。
总之 单例 非static成员变量及static成员变量都是线程不安全, 多例 非static成员变量线程安全,但多例static成员变量会有线程不安全情况,
关于synchronized的实现还要考虑死锁等情况,下回有时间再写一下,本文有写的不好不对的地方请指出,大家一起探讨,谢谢!