【并发编程】线程范围内的数据共享,ThreadLocal,多线程间的数据共享


这几天生病了,所以学的比较少,只学习了关于线程中的线程范围数据共享,ThreadLocal以及多线程数据共享。

在这里记下我的理解和代码,其实理解都写在代码注释里了,个人觉得这样更加适合以后复习。


(上一篇我讲的是关于线程的创建,计时器,同步锁,线程间的通信。)

第一个,关于线程范围内的数据共享。

数据共享,一般是多个事件在串在同一个线程里调用同一个数据。下面一个小例子就可以说明清楚:

package Multithreading;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class ThreadScopeShareData {
	//private static int data = 0;
	private static Map<Thread,Integer> threadData = 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 data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName()
							+ " has put data : " + data);
					threadData.put(Thread.currentThread(), data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}

	static class A {
		public int get() {
			int data = threadData.get(Thread.currentThread());
			System.out.println("A from " + Thread.currentThread().getName()
					+ " has put data : " + data);
			return data;
		}
	}

	static class B {
		public int get() {
			int data = threadData.get(Thread.currentThread());
			System.out.println("B from " + Thread.currentThread().getName()
					+ " has put data : " + data);
			return data;
		}
	}
}

运行结果是:


不过上面的例子只是说明线程范围的数据共享。一般在开发时,我们使用ThreadLocal这个东西。


第二个,关于ThreadLocal:

ThreadLocal实现线程范围的共享变量:用于实现线程内的数据共享, 即对于相同的程序代码,多个模块在同一个线程中运行时要共享的一份数据,而在另外线程中运行时又共享另外一份数据。

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。


ThreadLocal的应用场景:

 * 1.订单处理包含一系列操作:减少库存量,增加一条流水台帐,修改总账,这几个操作要在同一个事务完成,也就是在同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
 * 2.银行转账包含一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象, 转入和转出操作的代码分别是两个不同的账户对象的方法
 * 3.例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中调用getContext方法,拿到的都是同一个。


这面的代码可以清晰表现出ThreadLocal的应用:

MyThreadScopeData.java

package Multithreading;

class MyThreadScopeData {
	/**
	 *  单例模式的演变
	 */
	private MyThreadScopeData() {}// 防止外部使用new
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();

	public static/* synchronized */MyThreadScopeData getThreadInstance() {
		/**
		 * 此处可以不加synchronized,因为A线程进来,instance如果为null,就会执行new,如果在其未返回值前线程切换,
		 * B线程进来, 获取instance,发现其值为空,紧接着会执行new,但是这个两个不同的线程,操作的是不同的数据,
		 * 其创建的实例属于各线程自己的实例,互不影响,所以这里不需要同步锁。
		 */
		MyThreadScopeData instance = map.get();
		if (instance == null) {
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}

	private String name;
	private int age;
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

ThreadLocalTest.java

package Multithreading;

import java.util.Random;

public class ThreadLocalTest {
	public static void main(String[] args) {
		for (int i = 0; i < 2; i++) {
			new Thread(new Runnable() {
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName()
							+ " has put data : " + data);

					MyThreadScopeData.getThreadInstance().setName("name:" + data);
					MyThreadScopeData.getThreadInstance().setAge(data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}

	static class A {
		public void get() {
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("A from "+Thread.currentThread().getName() + " getMyData: "
					+ myData.getName());
			System.out.println("A from "+Thread.currentThread().getName() + " getMyData: "
					+ myData.getAge());
		}
	}

	static class B {
		public void get() {
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("B from "+Thread.currentThread().getName() + " getMyData: "
					+ myData.getName());
			System.out.println("B from "+Thread.currentThread().getName() + " getMyData: "
					+ myData.getAge());
		}
	}
}

其中利用的单例模式的演变形式,可以通过获取实例来得到对象,而不是new一个对象。

 * 总结:一个ThreadLocal代表一个变量,故其中只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。如果有一个自变量要线程共享呢?那请先定义一个对象来装这个自变量, 然后在ThreadLocal中存储这个对象。


第三个,多线程间的数据共享

如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有共享数据,例如:卖票系统就可以这么做。
如果每个线程执行的代码不同,这时候需要用不同的Runnable对象, 有如下两种方式来实现这些Runnable对象之间的数据共享:
 * 1.将共享数据封装在A对象中,然后将A对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到各个Runnable对象中去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
 * 2.将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量, 每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。
 * 3.上面两种方式的组合:将共享数据封装在A对象中,每个线程对共享数据的操作方法也分配到A对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量, 每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

总之,要同步互斥的几段代码分别放在几个独立的方法中,然后把这些方法再放在同一个类中, 这样比较容易实现他们之间的同步互斥和通信。
 ********* 另外的例子在《面试宝典》的第28题。


下面有一个小例子:

package Multithreading;

public class MultiThreadShareData {
	public static void main(String[] args) {
		final ShareData1 data1 = new ShareData1();
		new Thread(new Runnable() {
			public void run() {
				data1.increment();
			}
		}).start();
		new Thread(new Runnable() {
			public void run() {
				data1.decrement();
			}
		}).start();
	}
}

class ShareData1 {

	private int j = 0;;

	public synchronized void increment() {
		j++;
	}

	public synchronized void decrement() {
		j--;
	}

}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值