如何实现线程安全

什么是线程安全

个人理解: 多个线程同时读写某实例对象中同一数据, 可能会造成数据的不正确结果, 这就是线程不安全.
在操作数据时, 避免同一数据同一时刻被多个线程共享, 就不会造成数据的混乱, 这就是线程安全.

//线程不安全简单示例
public class ThreadSafeDemo {
    int index = 0;
    List<String> testList = new ArrayList<String>();
    public static void main(String[] args) throws InterruptedException {
        new ThreadSafeDemo().test();
    }

    private void test() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100000; i++) {
            executorService.submit(() -> {
                index++;
            });
            if (i < 1000) {
                executorService.submit(new TestThread(testList));
            }
        }
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            Thread.sleep(100);
        }
        System.out.println("index: " + index);
        System.out.println("testList : " + testList.size());
    }
}

class TestThread implements Runnable {

    public List<String> list;

    public TestThread(List<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            list.add("");
        }
    }
}
// 多次测试, 两个输出很大几率都不能输出100000

为什么会造成线程不安全

一个变量的赋值(i++, list 的add方法)看似是原子操作, 其实是分为三个步骤执行:

  1. 复制源数据, 生成一个数据副本
  2. 操作数据副本
  3. 将副本数据写入源数据
    当两个线程同时读取 index 时, 都读取到0, 执行index++ 之后, 又写回源数据, 这样本来应该是2 的源数据就变成了1.

如何实现线程安全

从上面的示例可以看出, 如果将共享的资源index加锁, 一次只有一个线程访问, 那么就可以解决这个问题.
从这个思路出发 可以得出:
1. 直接使用java的 synchronized, lock 等加锁 或者 AtomicInteger 等原子操作类, 让共享资源的访问从并行化变成串行化.
2. 如果是集合类型的并发操作时, 直接使用concurrent包下的 并发集合, 也能达到1的效果.

public class ThreadSafeDemo {
    int index = 0;
    ArrayBlockingQueue<String> testList = new ArrayBlockingQueue<String>(100000);
    private static final Object objLock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new ThreadSafeDemo().test();
    }

    private void test() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100000; i++) {
            executorService.submit(() -> {
                synchronized (objLock) {
                    index++;
                }
            });
            if (i < 1000) {
                executorService.submit(new TestThread(testList));
            }
        }
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            Thread.sleep(100);
        }
        System.out.println("index: " + index);
        System.out.println("testList : " + testList.size());
    }
}

class TestThread implements Runnable {

    public ArrayBlockingQueue<String> list;

    public TestThread(ArrayBlockingQueue<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            list.add("");
        }
    }
}

// 输出都是正确的 100000

还有另外一种思路, 就是为每个执行的线程单独准备一份源数据, 不去一个线程中共享同一个源数据.
1. 对应的就是 ThreadLocal 类, 将每个线程的数据独立, 分别计算, 排除共享变量.

public class ThreadLocalTest {
    ThreadLocal<String> localString = new ThreadLocal<String>();

    public void set() {
        localString.set(Thread.currentThread().getName());
    }

    public String getString() {
        return localString.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final ThreadLocalTest test = new ThreadLocalTest();

        test.set();
        System.out.println(test.getString());

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 2; i++) {
            executorService.submit(() -> {
                test.set();
                System.out.println(test.getString());
            });

        }
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            Thread.sleep(100);
        }
        System.out.println(test.getString());
    }
}
// 输出: 
// main
// pool-1-thread-1
// pool-1-thread-2
// main 

总结

多线程编程中很容易引入线程安全问题, 在实际开发中必须注意.
实际开发中SimpleDataFormat不是一个线程安全的类, 所以不推荐使用静态方法的方式在多线程下访问其 parse() 和 format() 方法, 建议使用 ThreadLocal 或者加锁的方式调用.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值