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 = new HashMap<Thread, Integer>(); /** * @param args */ public static void main(String[] args) { ThreadScopeShareData tShareData = new ThreadScopeShareData(); tShareData.init(); } /** * 定义初始化方法, * 解决main(静态)方法不能创建内部类实例对象 */ public void init() { FirstThread firstThread = new FirstThread(); Thread fThread = new Thread(firstThread); fThread.start(); SecondThread secondThread = new SecondThread(); secondThread.start(); } //模块A static class A { public void get() { int data = threadData.get(Thread.currentThread());//从线程获取数据 System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); } } // 模块B static class B { public void get() { int data = threadData.get(Thread.currentThread());//从线程获取数据 System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); } } /* * 线程一 */ class FirstThread implements Runnable { @Override public void run() { int data = new Random().nextInt(); threadData.put(Thread.currentThread(), data);//把数据放入MAP System.out.println(Thread.currentThread().getName() + " has put data :" + data); new A().get(); new B().get(); } } /* * 线程二 */ class SecondThread extends Thread { @Override public void run() { int data = new Random().nextInt(); threadData.put(Thread.currentThread(), data);//放入MAP System.out.println(Thread.currentThread().getName() + " has put data :" + data); new A().get(); new B().get(); } } }
【输出】
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 = new HashMap<Thread,Integer>(); //用 ThreadLocal 取代 Map<Thread,data>。 private static ThreadLocal<Integer> xThreadLocal = new ThreadLocal<Integer>(); /** * @param args */ public static void main(String[] args) { ThreadScopeShareData tShareData = new ThreadScopeShareData(); tShareData.init(); } /** * 定义初始化方法, 解决main(静态)方法不能创建内部类实例对象 */ public void init() { FirstThread firstThread = new FirstThread(); Thread fThread = new Thread(firstThread); fThread.start(); SecondThread secondThread = new SecondThread(); secondThread.start(); } // 模块A static class A { public void get() { // int data = threadData.get(Thread.currentThread());//从线程获取数据 int data = xThreadLocal.get();// 直接获取即可,ThreadLocal自动会区分线程 System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); } } // 模块B static class B { public void get() { // int data = threadData.get(Thread.currentThread());//从线程获取数据 int data = xThreadLocal.get();// 直接获取即可,ThreadLocal自动会区分线程 System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); } } /* * 线程一 */ class FirstThread implements Runnable { @Override public void run() { int data = new Random().nextInt(); // threadData.put(Thread.currentThread(), data);//把数据放入MAP xThreadLocal.set(data);// 直接放数据,取代那个map System.out.println(Thread.currentThread().getName() + " has put data :" + data); new A().get(); new B().get(); } } /* * 线程二 */ class SecondThread extends Thread { @Override public void run() { int data = new Random().nextInt(); // threadData.put(Thread.currentThread(), data);//放入MAP xThreadLocal.set(data);// 直接放数据,取代那个map System.out.println(Thread.currentThread().getName() + " has put data :" + data); new A().get(); new B().get(); } } }
可以使用单例,并且利用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 = 0; private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();// 传递数据 private static ThreadLocal<Integer> xThreadLocal = new ThreadLocal<Integer>(); private static ThreadLocal<MyData> myDataThreadLocal = new ThreadLocal<MyData>(); /** * @param args */ public static void main(String[] args) { ThreadLocalTest tThreadLocalTest = new ThreadLocalTest(); tThreadLocalTest.init(); } /** * 定义初始化方法, 解决main(静态)方法不能创建内部类实例对象 */ public void init() { FirstThread firstThread = new FirstThread(); Thread fThread = new Thread(firstThread); fThread.start(); SecondThread secondThread = new SecondThread(); Thread sThread = new Thread(secondThread); sThread.start(); } static class A { public void get() { // int data = threadData.get(Thread.currentThread()); int data = xThreadLocal.get();// 利用ThreadLocal直接拿数据 // MyData myData = myDataThreadLocal.get(); // System.out.println(myData.toString()); MyNewData myNewData = MyNewData.getThreadInstance(); System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data + "," + myNewData.getName() + "," + myNewData.getAge()); } } static class B { public void get() { // int data = threadData.get(Thread.currentThread()); int data = xThreadLocal.get();// 利用ThreadLocal直接拿数据 // MyData myData = myDataThreadLocal.get(); // System.out.println(myData.toString()); MyNewData myNewData = MyNewData.getThreadInstance(); System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data + "," + myNewData.getName() + "," + myNewData.getAge()); } } /* * 线程一 */ class FirstThread implements Runnable { @Override public void run() { int data = new Random().nextInt(); // threadData.put(Thread.currentThread(), data);//放入MAP xThreadLocal.set(data);// 利用ThreadLocal存放数据 System.out.println(Thread.currentThread().getName() + " has put data :" + data); // 简单对象 // MyData myData = new MyData("shaoxiaobao",3); // myDataThreadLocal.set(myData); // 单例对象 MyNewData myNewData = MyNewData.getThreadInstance(); myNewData.setAge(data); myNewData.setName("name" + data); new A().get(); new B().get(); } } /* * 线程二 */ class SecondThread extends Thread { // private int data; // public SecondThread(int data) { // this.data = data; // } @Override public void run() { int data = new Random().nextInt(); // threadData.put(Thread.currentThread(), data);//放入MAP xThreadLocal.set(data);// 利用ThreadLocal存放数据 System.out.println(Thread.currentThread().getName() + " has put data :" + data); // 简单对象 // MyData myData = new MyData("zhaoxioaniu",2); // myDataThreadLocal.set(myData); // 单例对象 MyNewData myNewData = MyNewData.getThreadInstance(); myNewData.setAge(data); myNewData.setName("name" + data); new A().get(); new B().get(); } } /** * @title 简单写法 * @description * @author SAM-SHO * @Date 2014-8-23 */ class MyData { private String name; private int age; public MyData() { }; public MyData(String name, int age) { super(); this.name = name; this.age = 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; } @Override public String toString() { return "MyData [age=" + age + ", name=" + name + "]"; } } } /** * @title 单利数据 高级实现 交给数据类的对象 * @description * @author SAM-SHO * @Date 2014-8-23 */ class MyNewData { private MyNewData() { };// 私有构造方法 /* * private static MyNewData instance = null; * * //懒汉单例:防止内存中存在两个单例的对象,要互斥,效率太低 public static synchronized MyNewData * getInstance() { * * if(instance==null) { instance = new MyNewData(); } return instance; } */ // 改造单例,可以不加互斥 private static ThreadLocal<MyNewData> map = new ThreadLocal<MyNewData>();// 定义ThreadLocal public static MyNewData getThreadInstance() { MyNewData instance = map.get();// ThreadLocal先拿,没有就创建 if (instance == null) { instance = new MyNewData(); map.set(instance);// 创建完,就放入ThreadLocal } 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; } @Override public String toString() { return "MyData [age=" + age + ", name=" + name + "]"; } }【输出】
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 = new ShareData();//同一个对象 OneThread oneThread= new OneThread(tShareData); Thread thread1 = new Thread(oneThread); thread1.start(); TwoThread twoThread= new TwoThread(tShareData); Thread thread2 = new Thread(twoThread); thread2.start(); ThirdThread thirdThread= new ThirdThread(tShareData); Thread thread3 = new Thread(thirdThread); thread3.start(); FourThread fourThread= new FourThread(tShareData); Thread thread4 = new Thread(fourThread); thread4.start(); final ShareData mShareData = new ShareData(); } class OneThread implements Runnable{ private ShareData tShareData; public OneThread() {} public OneThread(ShareData tShareData) { this.tShareData = tShareData; } @Override public void run() { while(true) { tShareData.add(); } } } class TwoThread implements Runnable{ private ShareData tShareData; public TwoThread(ShareData tShareData) { this.tShareData = tShareData; } @Override public void run() { while(true) { tShareData.add(); } } } class ThirdThread implements Runnable{ private ShareData tShareData; public ThirdThread(ShareData tShareData) { this.tShareData = tShareData; } @Override public void run() { while(true) { tShareData.minus(); } } } class FourThread implements Runnable{ private ShareData tShareData; public FourThread(ShareData tShareData) { this.tShareData = tShareData; } @Override public void run() { while(true) { tShareData.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); } } }
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)总之,要同步互斥的极几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥与通信。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。