zoukankan      html  css  js  c++  java
  • Java并发基础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那边就出问题了。

    线程范围内共享数据的问题就总结这么多吧~如果有问题,欢迎指正,我们一起进步!

  • 相关阅读:
    PNG文件格式具体解释
    opencv2对读书笔记——使用均值漂移算法查找物体
    Jackson的Json转换
    Java实现 蓝桥杯VIP 算法训练 装箱问题
    Java实现 蓝桥杯VIP 算法训练 装箱问题
    Java实现 蓝桥杯VIP 算法训练 单词接龙
    Java实现 蓝桥杯VIP 算法训练 单词接龙
    Java实现 蓝桥杯VIP 算法训练 方格取数
    Java实现 蓝桥杯VIP 算法训练 方格取数
    Java实现 蓝桥杯VIP 算法训练 单词接龙
  • 原文地址:https://www.cnblogs.com/eson15/p/10235528.html
Copyright © 2011-2022 走看看