多线程(三) 实现线程范围内模块之间共享数据及线程间数据独立(ThreadLocal)

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。JDK 1.2的版本中就提供java.lang.ThreadLocal,使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。

1.下图和辅助代码解释ThreadLocal的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

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

3.ThreadLocal的应用场景:
      (1)订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
      (2)银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
      (3)例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。
  4.实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。
  5.实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。
      (1)对基本类型的数据的封装,这种应用相对很少见。
      (2)对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。

例子程序:

第一种实现方式:(但是这种实现方式不如第二种实现方式好)

package cn.itcast.lishehe;

import java.util.Random;
/** 李社河-2015年6月11日
 * 题目要求:构造两线程,要求:
 *    (1)两线程并发操作  (这就要求不能使用syschronized关键字)
 *    (2)要求两线程分别访问各自的数据MyData对象,互不干扰
 *      (这里就可以使用ThreadLocal对象,通过set()和get()即可获得与本线程相关的MyData对象,但注意,其只能关联一个数据,
 *        所以对于多个数据则应该封装到一个类中,其实际上也是通过Map实现的)
 *    (3)线程内有A、B两个模块,模块之间共享数据MyData数据
 *
 *   本程序实现方式不如ThreadLocalDataIndependent2.java好。
 **/
public class ThreadLocalDataIndependent1 {
     static ThreadLocal<MyData> threadLocal = new ThreadLocal<MyData>();
     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(); //这里的data必须定义为局部变量,否则线程间不能实现数据独立
                    MyData myData = new MyData();
                    myData.setName("name"+data);
                    myData.setAge(data);
                    threadLocal.set(myData);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }  

    static class A{
        public void get(){
            MyData myData = threadLocal.get();
            System.out.println("A from "+Thread.currentThread().getName()+" get MyData :"
                    +myData.getName()+","+myData.getAge());
        }
    }
    static class B{
        public void get(){
            MyData myData = threadLocal.get();
            System.out.println("B from "+Thread.currentThread().getName()+" get MyData :"
                    +myData.getName()+","+myData.getAge());
        }
    }
}
class MyData{
    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;
    }  

}  

运行结果

第二种实现方式:(struts2中对于用户的每次请求创建一个action实例,其也是这样实现的)

package cn.itcast.lishehe;

import java.util.Random;
/** 李社河-2015年6月11日
 * 题目要求:构造两线程,要求:
 *    (1)两线程并发操作  (这就要求不能使用syschronized关键字)
 *    (2)要求两线程分别访问各自的数据MyData对象,互不干扰
 *      (这里就可以使用ThreadLocal对象,通过set()和get()即可获得与本线程相关的MyData对象,但注意,其只能关联一个数据,
 *        所以对于多个数据则应该封装到一个类中,其实际上也是通过Map实现的)
 *    (3)线程内有A、B两个模块,模块之间共享数据MyData数据
 *
 *   本程序实现方式比ThreadLocalDataIndependent1.java要好。
 *   其中Struts2对于用户的每次请求,都将创建一个action实例进行处理,每个线程都有其独立的数据,其实现方式就是这种。
 **/
public class ThreadLocalDataIndependent2 {
     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(); //这里的data必须定义为局部变量,否则线程间不能实现数据独立
                    MyData2.getInstance().setName("name"+data);
                    MyData2.getInstance().setAge(data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }  

    static class A{
        public void get(){
            MyData2 myData = MyData2.getInstance();
            System.out.println("A from "+Thread.currentThread().getName()+" get MyData :"
                    +myData.getName()+","+myData.getAge());
        }
    }
    static class B{
        public void get(){
            MyData2 myData = MyData2.getInstance();
            System.out.println("B from "+Thread.currentThread().getName()+" get MyData :"
                    +myData.getName()+","+myData.getAge());
        }
    }
}
class MyData2{
    private static ThreadLocal<MyData2> threadMap = new ThreadLocal<MyData2>();
    private MyData2(){  

    }
    //这里无需使用syschronized关键字
    public static MyData2 getInstance(){
        MyData2 myData = threadMap.get();
        if(myData==null){
            myData = new MyData2();
            threadMap.set(myData);
        }
        return myData;
    }  

    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;
    }  

}  

运行结果

时间: 2024-11-05 23:00:50

多线程(三) 实现线程范围内模块之间共享数据及线程间数据独立(ThreadLocal)的相关文章

多线程(四) 实现线程范围内模块之间共享数据及线程间数据独立(Map集合)

多个线程访问共享对象和数据的方式 1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 2.如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:            将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象.每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个

如何实现线程范围内共享数据 -- ThreadLocall类及其应用技巧

目标:如何保证各自线程上的数据是独立的,即A线程上数据只能被A线程操作 1:示例线程共享变量 我们先来看一个反例 package com.prepare.study; import java.util.Random; /** * @author: yinlm * @Date: Created in 2018/4/18 * @Description:多个线程数据混乱的示例 */ public class ThreadTest2 { // static 修饰 表示这是个全局变量 private st

JAVA多线程提高三:线程范围内共享变量&amp;ThreadLocal

今天我们学习的是如何在线程自己的范围内达到变量数据的共享,而各个线程之间又是互相独立开来,各自维护的,即我们说的ThreadLocal的作用. 一.概念 可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据,就可以实现线程范围内共享相同的变量. 二.代码 Runnable中的run()方法里面执行Thread.currentThread()都会对应当前Runnable对应的线程,因此A.B中对应的Thread.currentThread

Java多线程——&lt;三&gt;简单的线程执行:Executor

一.概述 按照<Java多线程——<一><二>>中所讲,我们要使用线程,目前都是显示的声明Thread,并调用其start()方法.多线程并行,明显我们需要声明多个线程然后都调用他的start方法,这么一看,似乎有些问题:第一.线程一旦多了,声明势必是个问题:第二.多线程启动如果通过手动执行的话,那可能一个线程已经跑完了,另外一个还没起来(我推测可能会出现这个问题).所以,我们在想,如果有个管家,能够帮我们管理这么多线程,只需要把我们定义的任务交给管家,管家就能够帮我们

多线程(三) java中线程的简单使用

============================================= 原文链接:多线程(三) java中线程的简单使用 转载请注明出处! ============================================= java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依旧是实现了Runnabel接口.考虑到java的

Android多线程研究(5)——线程之间共享数据

一.如果是每个线程都执行相同的代码,则可以使用同一个Runnable来实现共享 public class MultiThreadShareData { public static void main(String[] args) { new Thread(new ShareData()).start(); new Thread(new ShareData()).start(); } static class ShareData implements Runnable{ private int j

多线程-线程一打印1,2,3,4,5线程二打印6,7,8,9,10,线程三打印11,12,13,14,15,...直到45结束

多线程-线程一打印1,2,3,4,5线程二打印6,7,8,9,10,线程三打印11,12,13,14,15,...知道45结束 public class Exam3{ public static void main(String[]args){ MyThread t1=new MyThread("线程一"); MyThread t2=new MyThread("线程二"); MyThread t3=new MyThread("线程三"); t1.

java多线程(三)-Executors实现的几种线程池以及Callable

从java5开始,类库中引入了很多新的管理调度线程的API,最常用的就是Executor(执行器)框架.Executor帮助程序员管理Thread对象,简化了并发编程,它其实就是在 提供了一个中间层,方便程序员管理异步任务的执行,而又不用显式的管理线程的生命周期. Executor采用了线程池实现,也更节约开销,因为是我们启动新线程的首选方法. 示例代码:src/thread_runnable/CachedThreadPool.java 1 public class CachedThreadPo

多线程之线程范围内的数据共享ThreadLocal

如果多个线程使用同一个数据,那么如何保证线程范围内的数据共享. 我们可以使用一个map来存储当前线程,以及其数据如下: package andy.thread.traditional.test; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * @author Zhang,Tianyou * @version 2014年11月8日 下午2:12:44 */ // 线程范围内的共享数据 pu