• 线程范围内共享数据


    假设现在有个公共的变量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那边就出问题了。

  • 相关阅读:
    学习进度14
    学习进度13
    学习进度12
    2020.12.01 表单元素
    2020.12.02 表单属性
    2020.12.04
    2020.12.06
    2020.12.07
    2020.12.08 URL
    2020.12.09
  • 原文地址:https://www.cnblogs.com/justuntil/p/7560925.html
Copyright © 2020-2023  润新知