【并发技术06】线程范围内共享数据

简介: 【并发技术06】线程范围内共享数据

假设现在有个公共的变量 data,有不同的线程都可以去操作它,如果在不同的线程对 data 操作完成后再去取这个 data,那么肯定会出现线程间的数据混乱问题,因为 A 线程在取 data 数据前可能 B 线程又对其进行了修改,下面写个程序来说明一下该问题:

public class ThreadScopeShareData {
    private static int data = 0;//公共的数据
    public static void main(String[] args) {
        for(int i = 0; i < 2; i ++) { //开启两个线程
            new Thread(new Runnable() { 
                @Override
                public void run() {
                    int temp = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put a data: " + temp); //打印出来为了看效果
                    data = temp; //操作数据:赋新值
                    new TestA().getData();
                    new TestB().getData();
                }
            }).start();
        }
    }
    static class TestA {
        public void getData() {
            System.out.println("A get data from " + Thread.currentThread().getName() + ": " + data);//取出公共数据data
        }
    }
    static class TestB {
        public void getData() {
            System.out.println("B get data from " + Thread.currentThread().getName() + ": " + data);
        }
    }
}

我们来看一下打印出来的结果:

Thread-0 has put a data: -1885917900

Thread-1 has put a data: -1743455464

A get data from Thread-0: -1743455464

A get data from Thread-1: -1743455464

B get data from Thread-1: -1743455464

B get data from Thread-0: -1743455464

从结果中可以看出,两次对 data 赋的值确实不一样,但是两个线程最后打印出来的都是最后赋的那个值,说明 Thread-0 拿出的数据已经不对了,这就是线程间共享数据带来的问题。

当然,我们完全可以使用 synchronized 关键字将 run() 方法中的几行代码给套起来,这样每个线程各自执行完,打印出各自的信息,这是没问题的,确实可以解决上面的线程间共享数据问题。但是,这是以其他线程被阻塞为代价的,即 Thread-0 在执行的时候,Thread-1 就被阻塞了,必须等待 Thread-0 执行完了才能执行。

那么如果我想两个线程同时跑,并且互不影响各自取出的值,该怎么办呢?这也是本文所要总结的重点,解决该问题的思想是:虽然现在都在操作公共数据 data,但是不同的线程本身对这个 data 要维护一个副本,这个副本不是线程间所共享的,而是每个线程所独有的,所以不同线程中所维护的 data 是不一样的,最后取的时候,是哪个线程,我就从哪个线程中取该 data。

基于上面这个思路,我们再把上面的程序做一修改,如下:

public class ThreadScopeShareData {
    private static int data = 0;//公共的数据
    //定义一个Map以键值对的方式存储每个线程和它对应的数据,即Thread:data
    private static Map<Thread, Integer> threadData = Collections.synchronizedMap(new HashMap<Thread, Integer>());   
    public static void main(String[] args) {
        for(int i = 0; i < 2; i ++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int temp = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put a data: " + temp); //打印出来为了看效果                  
                    threadData.put(Thread.currentThread(), temp); //向Map中存入本线程data数据的一个副本
                    data = temp; //操作数据:赋新值
                    new TestA().getData();
                    new TestB().getData();
                }
            }).start();
        }
    }
    static class TestA {
        public void getData() {
            System.out.println("A get data from " + Thread.currentThread().getName() + ": " 
                + threadData.get(Thread.currentThread())); //取出各线程维护的那个副本
        }
    }
    static class TestB {
        public void getData() {
            System.out.println("B get data from " + Thread.currentThread().getName() + ": " 
                + threadData.get(Thread.currentThread()));
        }
    }
}

上面程序中维护了一个 Map,键值对分别是线程和它的数据,那么在操作 data 的时候,先把各自的数据保存到这个 Map 中,这样每个线程保存的肯定不同,当再取的时候,根据当前线程对象作为 key 来取出对应的 data 副本,这样不同的线程之间就不会相互影响了。这个 HashMap 也需要包装一下,因为 HashMap 是非线程安全的,上面的程序中,不同的线程有对 HashMap 进行写操作,就有可能产生并发问题,所以也要包装一下。最后来看一下执行结果:

Thread-0 has put a data: 1817494992

Thread-1 has put a data: -1189758355

A get data from Thread-0: 1817494992

A get data from Thread-1: -1189758355

B get data from Thread-0: 1817494992

B get data from Thread-1: -1189758355

就是线程范围内共享数据,即同一个线程里面这个数据是共享的,线程间是不共享的。

这让我联想到了学习数据库的时候用到的 ThreadLocal,操作数据库需要 connection,如果当前线程中有就拿当前线程中存的 connection,否则就新建一个放到当前线程中,这样就不会出现问题,因为每个线程本身共享了一个 connection,它不是线程间共享的。这也很好理解,这个 connection 肯定不能共享,假设 A 和 B 用户都拿到这个 connection 并开启了事务,现在 A 开始转账了,但是钱还没转好,B 转好了关闭了事务,那么 A 那边就出问题了。线程范围内共享数据的问题就总结这么多吧。


相关文章
|
29天前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
134 0
|
14天前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
124 59
|
5天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
24天前
|
缓存 安全 Java
使用 Java 内存模型解决多线程中的数据竞争问题
【10月更文挑战第11天】在 Java 多线程编程中,数据竞争是一个常见问题。通过使用 `synchronized` 关键字、`volatile` 关键字、原子类、显式锁、避免共享可变数据、合理设计数据结构、遵循线程安全原则和使用线程池等方法,可以有效解决数据竞争问题,确保程序的正确性和稳定性。
33 2
|
26天前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
25 1
|
27天前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
51 0
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
38 1
C++ 多线程之初识多线程
|
16天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
16天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
13 2
|
16天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2