线程安全(单例与多例)

      又周五了,时间过得好快,住在集体宿舍,几个宅男共处一室好是无聊,习惯性来到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();

实体的第一行代码 修改成非static成员变量
  private  ThreadLocal count = new ThreadLocal();

运行如下:

B5
A0
B6
B7
A1
B8
B9
A2
A3
A4

已完成线程安全化。 单例 static成员变量也是线程不安全,一样可以通过ThreadLocal线程安全化。

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

关于synchronized的实现还要考虑死锁等情况,下回有时间再写一下,本文有写的不好不对的地方请指出,大家一起探讨,谢谢!




你可能感兴趣的:(JAVA,线程安全,单例,多例)