首页 > 代码库 > Java线程(二):线程数据共享及ThreadLocal使用

Java线程(二):线程数据共享及ThreadLocal使用

一、线程范围内共享数据

1、线程范围内共享数据是指要实现这三个对象在同一个线程上访问的数据的一致性,不同线程访问的差异性。

2、如有三个对象A、B、C,两个线程@,#,那么线程范围内共享数据就是指ABC在线程@上访问的数据是相同的。而三个对象访问 @和访问 # 的数据时不同的。 有几个线程就会有几份数据。如下图所示:




3、实现:利用Map<Thraed,data>来实现,Map的Key是关键。

4、代码实现:

1)线程会访问模块;

2)线程负责放数据;

3)模块负责从访问的线程中拿数据。

4)验证单个线程在访问不同模块时是否共享数据??


package com.Thread;

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

/**
 * 
 * ThreadScopeShareData.java
 *
 * @title 线程范围内共享变量 
 * @description 各模块(A/B)在某个线程上的数据是一致的:线程范围内的数据
 * @author SAM-SHO 
 * @Date 2014-8-17
 */
public class ThreadScopeShareData {
	
	//传递数据的容器,这个是关键
	// map的key为线程
	private static Map<Thread,Integer> threadData = http://www.mamicode.com/new HashMap();>
【输出】

Thread-0 has put data :372405571
Thread-1 has put data :-1925902112
A from Thread-0 get data :372405571
A from Thread-1 get data :-1925902112
B from Thread-0 get data :372405571
B from Thread-1 get data :-1925902112

5、分析:

1)线程0放入 ‘372405571’ ,线程1放入 ‘ -1925902112’

2)A模块从线程0 取出 372405571, B模块从线程0 取出 372405571。即共享了数据。

3)A模块从线程1 取出 -1925902112, B模块从线程1 取出 -1925902112。即共享了数据。



二、ThreadLocal的使用

1、ThreadLocal 可以轻松实现线程范围内的数据共享,即使用ThreadLocal<data> 取代上面的Map<Thread,data>。

2、使用:每个线程使用调用ThreadLocal 的set()方法放入共享数据,而每个模块只需要调用get()获取即可,非常简单。

修改上面代码如下:

package com.practise.tradition;

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

import com.Thread.ThreadScopeShareData;

/**
 * 
 * ThreadScopeData.java
 * 
 * @title 线程范围内的共享数据
 * @description 各模块(A/B)在某个线程上的数据是一致的:线程范围内的数据
 * @author SAM-SHO
 * @Date 2014-11-8
 */
public class ThreadScopeData {

	// 传递数据的容器,这个是关键
	// map的key为线程
	// private static Map<Thread,Integer> threadData = http://www.mamicode.com/new HashMap();>


3、共享复杂数据,如是一个数据类。

可以使用单例,并且利用ThreadLocal 改造单例,实现线程安全。

package com.Thread;

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

/**
 * 
 * ThreadLocalTest.java
 * 
 * @title ThreadLocal失效线程内数据共享
 * @description
 * @author SAM-SHO
 * @Date 2014-8-23
 */
public class ThreadLocalTest {

	private static int data = http://www.mamicode.com/0;>【输出】

Thread-0 has put data :1074769954
Thread-2 has put data :-617831879
A from Thread-0 get data :1074769954,name1074769954,1074769954
A from Thread-2 get data :-617831879,name-617831879,-617831879
B from Thread-0 get data :1074769954,name1074769954,1074769954
B from Thread-2 get data :-617831879,name-617831879,-617831879


三、多个线程共享数据

问题:四个线程,其中2个线程每次对J增加1,另外2个线程对J减少1。

1、如果每个线程执行的代码相同,可以使用同一 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如卖系统就可以这么做。

 2、如果每个线程执行的代码不同,这时候需要不同的 Runnable 对象,有如下两种方式来实现这些 Runnable 对象之间的数据共享。

1)将共享数据(J)封装在另外一个对象(ShareData)中,然后将这个对象逐一传递给各个 Runnable 对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成。这样容易实现针对该数据进行的各个操作的互斥与通信。如:

package com.Thread;


/**
 * 
 * MultiThreadShareData.java
 *
 * @title 多个线程共享数据
 * @description
 * 四个线程,其中2个线程每次对J增加1,另外2个线程对J减少1.
 * @author SAM-SHO 
 * @Date 2014-8-23
 */
public class MultiThreadShareData {
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MultiThreadShareData ttt = new MultiThreadShareData();
		ttt.init();
	}
	
	/**
	 * 1、如果每个线程执行的代码相同,可以使用同一 Runnable 对象,
	 * 这个 Runnable 对象中有那个共享数据,例如卖票系统就可以这么做
	 * 2、如果每个线程执行的代码不同,这时候需要不同的 Runnable 对象,
	 * 有如下两种方式来实现这些 Runnable 对象之间的数据共享。
	 * 1)将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个 Runnable 对象。
	 * 每个线程对共享数据的操作方法也分配到哪个对象身上去完成。这样容易实现针对该数据
	 * 进行的各个操作的互斥与通信()
	 * 2)将这些 Runnable 对象作为某一类中的内部类,共享数据作为这个外部类中的成员变量
	 * ,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥与通信
	 * ,作为内部类的各个 Runnable 对象调用外部类的这个方法。
	 * 
	 * 3)上面两种方式的结合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法
	 * 也分配到那个对象身上去完成。对象作为这个外部类中的成员变量或方法中的局部变量,每个线程
	 * 的 Runnable 对象作为外部类中的成员内部类或局部内部类
	 * 
	 * 总之,要同步互斥的极几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中
	 * ,这样比较容易实现它们之间的同步互斥与通信。
	 */
	private void init(){
		ShareData tShareData = http://www.mamicode.com/new ShareData();//同一个对象>

2)将这些 Runnable 对象作为某一类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥与通信,作为内部类的各个 Runnable 对象调用外部类的这个方法。如

package com.Thread;


/**
 * 
 * MultiThreadShareData.java
 *
 * @title 多个线程共享数据
 * @description
 * 四个线程,其中2个线程每次对J增加1,另外2个线程对J减少1.
 * @author SAM-SHO 
 * @Date 2014-8-23
 */
public class MultiThreadShareData2 {
	
	//共享数据作为外部类的成员变量
	private int j = 0 ;
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MultiThreadShareData2 tt = new MultiThreadShareData2();
		
		OneThread tOneThread = tt. new OneThread();
		TwoThread tTwoThread = tt. new TwoThread();
		
		//起两个线程
		for (int i = 0; i < 2; i++) {
			Thread t = new Thread(tOneThread);
			t.start();
			t = new Thread(tTwoThread);
			t.start();
		}
	}
	
	// 对共享数据的操作方法也分配给外部类
	// 方便实现互斥与通信
	private  synchronized void add() {
		j++;
		System.out.println(Thread.currentThread().getName()+ " 加法   ---->"+j);
		
	}
	
	// 对共享数据的操作方法也分配给外部类
	// 方便实现互斥与通信
	private synchronized void minus() {
		j--;
		System.out.println(Thread.currentThread().getName()+ " 减法   <----"+j);
		
	}
	
	
	// 内部类 Runnable 1
	class OneThread implements Runnable{

		@Override
		public void run() {
			for (int i = 0; i < 10; i++) {
				add();
			}
		}
		
	}
	
	// 内部类 Runnable 2
	class TwoThread implements Runnable{
		@Override
		public void run() {
			for (int i = 0; i < 10; i++) {
				minus();
			}
		}
		
	}	

	
	/**
	 * 业务资源类
	 * MultiThreadShareData.java
	 *
	 * @title 
	 * @description
	 * @author SAM-SHO 
	 * @Date 2014-11-11
	 */
	class ShareData {
		private int j = 0;
		
		//加法
		public synchronized void add() {
			j++;
			System.out.println(Thread.currentThread().getName()+ " 加法   ---->"+j);
		}
		
		//加法
		public synchronized void minus() {
			j--;
			System.out.println(Thread.currentThread().getName()+ " 减法   <----"+j);
		}	
	}
}


3)上面两种方式的结合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成。对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的 Runnable 对象作为外部类中的成员内部类或局部内部类

4)总之,要同步互斥的极几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥与通信。


Java线程(二):线程数据共享及ThreadLocal使用