多线程通过构造传入的集合是值传递还是引用传递

浪腾度易SB 发布于 2016/05/05 13:55
阅读 261
收藏 0

今天编码过程中,需要将一个集合通过构造的方式传给一个实现Runnable的线程类,并启动多个线程同时处理数据.

此时想到值传递还是引用传递的问题,担心某个线程执行后会改变集合数据,但是运行demo发现完全没有影响

从现象上看类似于每启动一个线程,都将集合copy了一份,完全独立的值传递而不是引用传递

线程类:

package dts.test;


import java.util.ArrayList;

import org.apache.log4j.Logger;

class MarketDataCopyThread implements Runnable{
	protected final static Logger logger = Logger.getLogger(MarketDataCopyThread.class);
	private  ArrayList<String> dataMsgs;
	private  ArrayList<String> dataMsgsBackup;
	private String count;
    
    public MarketDataCopyThread(ArrayList<String> dataMsgs,String count){
    	this.dataMsgs = dataMsgs;
    	this.dataMsgsBackup = null;
    	this.count = count;
    }
	public void run ( ) {
		logger.info("线程"+count+"启动前的传入集合为:"+dataMsgs);
		synchronized (MarketDataCopyThread.class){ //交换,同步
		    ArrayList<String> temp = dataMsgsBackup;		
		    dataMsgsBackup = dataMsgs;
		    dataMsgs = temp;
		}
		//省略业务逻辑.............
		try {
			Thread.sleep(10*Integer.valueOf(count));
		} catch (NumberFormatException | InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		logger.info("线程"+count+"结束时的传入集合为:"+dataMsgs);
	}
	public ArrayList<String> getDataMsgs() {
		return dataMsgs;
	}
	public void setDataMsgs(ArrayList<String> dataMsgs) {
		this.dataMsgs = dataMsgs;
	}
	public ArrayList<String> getDataMsgsBackup() {
		return dataMsgsBackup;
	}
	public void setDataMsgsBackup(ArrayList<String> dataMsgsBackup) {
		this.dataMsgsBackup = dataMsgsBackup;
	}
	public String getCount() {
		return count;
	}
	public void setCount(String count) {
		this.count = count;
	}

	
	
}



测试类:

package dts.test;

import java.util.ArrayList;

public class ThreadArrayListTest {
	
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<String>();
		list.add("aac");
		list.add("bbc");
		list.add("ccc");
		for (int i = 0; i < 100; i++) {
			if(i==1){
				new Thread(new MarketDataCopyThread(list,i+"")).start();
				try {
					//等待第一个线程执行完成
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else if(i==99){
				//启动第二个线程
				new Thread(new MarketDataCopyThread(list,i*10+"")).start();
			}
		}
		
	}
}



运行结果:

log4j:ERROR Could not find value for key log4j.appender.error
log4j:ERROR Could not instantiate appender named "error".
2016-05-05 13:36:32,875  INFO (dts.test.MarketDataCopyThread:25) - 线程1启动前的传入集合为:[aac, bbc, ccc]
2016-05-05 13:36:32,890  INFO (dts.test.MarketDataCopyThread:38) - 线程1结束时的传入集合为:null
2016-05-05 13:36:42,875  INFO (dts.test.MarketDataCopyThread:25) - 线程990启动前的传入集合为:[aac, bbc, ccc]
2016-05-05 13:36:52,775  INFO (dts.test.MarketDataCopyThread:38) - 线程990结束时的传入集合为:null



有点费解,使用线程的构造过程中变成了值传递?

加载中
0
浪腾度易SB
浪腾度易SB

引用来自“xpbob”的评论

       synchronized (MarketDataCopyThread.class){ //交换,同步
            ArrayList<String> temp = dataMsgsBackup;      
            dataMsgsBackup = dataMsgs;
            dataMsgs = temp;

        }

你都让多个线程同步了,其实每次执行的只有一个线程,根本不会出现数据混乱。而且你只是交换了指向集合的引用而已,根本没有对内存的数据造成影响

线程类改为:

package dts.test;


import java.util.ArrayList;

import org.apache.log4j.Logger;

class MarketDataCopyThread implements Runnable{
	protected final static Logger logger = Logger.getLogger(MarketDataCopyThread.class);
	private  ArrayList<String> dataMsgs;
	private  ArrayList<String> dataMsgsBackup;
	private String count;
    
    public MarketDataCopyThread(ArrayList<String> dataMsgs,String count){
    	this.dataMsgs = dataMsgs;
    	this.dataMsgsBackup = null;
    	this.count = count;
    }
	public void run ( ) {
		logger.info("线程"+count+"启动前的传入集合为:"+dataMsgs);
		try {
			Thread.sleep(10*Integer.valueOf(count));
		} catch (NumberFormatException | InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		dataMsgs.clear();
		logger.info("线程"+count+"结束时的传入集合为:"+dataMsgs);
	}
	public ArrayList<String> getDataMsgs() {
		return dataMsgs;
	}
	public void setDataMsgs(ArrayList<String> dataMsgs) {
		this.dataMsgs = dataMsgs;
	}
	public ArrayList<String> getDataMsgsBackup() {
		return dataMsgsBackup;
	}
	public void setDataMsgsBackup(ArrayList<String> dataMsgsBackup) {
		this.dataMsgsBackup = dataMsgsBackup;
	}
	public String getCount() {
		return count;
	}
	public void setCount(String count) {
		this.count = count;
	}



测试类:

package dts.test;

import java.util.ArrayList;

public class ThreadArrayListTest {
	
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<String>();
		list.add("aac");
		list.add("bbc");
		list.add("ccc");
		//公用一个线程类
		MarketDataCopyThread dataCopyThread = new MarketDataCopyThread(list,1+"");
		for (int i = 0; i < 100; i++) {
			if(i==1){
				new Thread(dataCopyThread).start();
				try {
					//等待第一个线程执行完成
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else if(i==99){
				//启动第二个线程
				new Thread(dataCopyThread).start();
			}
		}
		System.out.println(list);
	}
}



运行结果:

log4j:ERROR Could not find value for key log4j.appender.error
log4j:ERROR Could not instantiate appender named "error".
2016-05-05 14:34:49,262  INFO (dts.test.MarketDataCopyThread:20) - 线程1启动前的传入集合为:[aac, bbc, ccc]
2016-05-05 14:34:49,278  INFO (dts.test.MarketDataCopyThread:34) - 线程1结束时的传入集合为:[]
[]
2016-05-05 14:34:59,262  INFO (dts.test.MarketDataCopyThread:20) - 线程1启动前的传入集合为:[]
2016-05-05 14:34:59,279  INFO (dts.test.MarketDataCopyThread:34) - 线程1结束时的传入集合为:[]



main方法中的list被清空了

浪腾度易SB
浪腾度易SB
回复 @xpbob : 是的,之前没有注意互换和改变数据的问题,被自己给误导了
xpbob
xpbob
引用互换和修改数据可不一样,举个例子,咱们手里有两个篮子ab,篮子里有4个水果,你原来的版本就是篮子互换,里面的水果还是4个,你现在的版本是你把水果扔了,把篮子给我,水果肯定少了啊。java只有值传递,指的是引用,而不是被指的数据,一个集合,无论引用有多少个,真正在内存的数据只有一份啊,改动了就不是原来的
0
景愿
景愿

说简单点,java中都是值传递,只不过在引用类型时传递的值是引用本身,或者叫指针

你在Thread中的代码(创建内部对象,改变他的引用地址为list的值)这些操作根本不会影响main中list的引用,及其引用的值,所以每个Thread都是拿到一样的引用!

我觉得我讲不清楚,希望你能理解

0
10书生
10书生
请关注synchronized部分代码,实际上根本集合根本就没有被多线程并发访问。
浪腾度易SB
浪腾度易SB
回复 @书_生 : 业务代码没必要贴出来吧,胸弟
10书生
10书生
回复 @小白941条狗 : 你想用代码说明问题,结果又贴的是不完全的代码,你闹呢?
浪腾度易SB
浪腾度易SB
是的,我省略一些本身的代码, 其他地方有并发,copy的时候没有把这些改掉
0
宿舍楼顶
宿舍楼顶
java中只有值传递
0
浪腾度易SB
浪腾度易SB

引用来自“景愿”的评论

说简单点,java中都是值传递,只不过在引用类型时传递的值是引用本身,或者叫指针

你在Thread中的代码(创建内部对象,改变他的引用地址为list的值)这些操作根本不会影响main中list的引用,及其引用的值,所以每个Thread都是拿到一样的引用!

我觉得我讲不清楚,希望你能理解

我把demo变了一下

package dts.test;

import java.util.ArrayList;

public class ThreadArrayListTest {
	
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<String>();
		list.add("aac");
		list.add("bbc");
		list.add("ccc");
		//公用一个线程类
		MarketDataCopyThread dataCopyThread = new MarketDataCopyThread(list,1+"");
		for (int i = 0; i < 100; i++) {
			if(i==1){
				new Thread(dataCopyThread).start();
				try {
					//等待第一个线程执行完成
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else if(i==99){
				//启动第二个线程
				new Thread(dataCopyThread).start();
			}
		}
		
	}
}



运行结果
log4j:ERROR Could not find value for key log4j.appender.error
log4j:ERROR Could not instantiate appender named "error".
2016-05-05 14:20:11,736  INFO (dts.test.MarketDataCopyThread:20) - 线程1启动前的传入集合为:[aac, bbc, ccc]
2016-05-05 14:20:11,750  INFO (dts.test.MarketDataCopyThread:34) - 线程1结束时的传入集合为:null
2016-05-05 14:20:21,736  INFO (dts.test.MarketDataCopyThread:20) - 线程1启动前的传入集合为:null
2016-05-05 14:20:21,746  INFO (dts.test.MarketDataCopyThread:34) - 线程1结束时的传入集合为:null


-------------------------------------------------

再打印了一下main方法中的list,确实两边没有关系


log4j:ERROR Could not find value for key log4j.appender.error
log4j:ERROR Could not instantiate appender named "error".
2016-05-05 14:26:35,515  INFO (dts.test.MarketDataCopyThread:20) - 线程1启动前的传入集合为:[aac, bbc, ccc]
2016-05-05 14:26:35,529  INFO (dts.test.MarketDataCopyThread:34) - 线程1结束时的传入集合为:null
main方法中的lsit :[aac, bbc, ccc]
2016-05-05 14:26:45,512  INFO (dts.test.MarketDataCopyThread:20) - 线程1启动前的传入集合为:null
2016-05-05 14:26:45,523  INFO (dts.test.MarketDataCopyThread:34) - 线程1结束时的传入集合为:null



0
xpbob
xpbob
       synchronized (MarketDataCopyThread.class){ //交换,同步
            ArrayList<String> temp = dataMsgsBackup;      
            dataMsgsBackup = dataMsgs;
            dataMsgs = temp;

        }

你都让多个线程同步了,其实每次执行的只有一个线程,根本不会出现数据混乱。而且你只是交换了指向集合的引用而已,根本没有对内存的数据造成影响

0
浪腾度易SB
浪腾度易SB

排除一些干扰问题后, 最终测试结果是:

线程中的集合做del或者clear操作会影响main方法中的集合

so:引用或者形参传递,线程中操作的集合的值,会改变内存中的集合,而不是copy后的数据副本

返回顶部
顶部