java多线程并发库高级应用 之 线程范围内共享数据

转自:http://blog.csdn.net/xushuaic/article/category/1335611

笔记摘要:

             所谓线程范围内共享数据,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据,

             API中为我们提供了一个操作线程范围内共享数据的类ThreadLocal,对于线程范围内共享数据的应用,在ThreadLocal的应用场景中进行了介绍,然后

             主要对它的使用进行讲解,演示了由单一数据的共享到将多个数据封装到一个对象中,然后进行共享。在开始先用一个Map集合简单实现线程范围内数据的共享


一、使用Map实现线程范围内数据的共享

 

原理:

    将线程对象作为map的键存入,这样就保证了map对象的唯一,也就保证了线程内数据的唯一

关键:  

    明确一点,把当前线程对象作为map集合的键存进去


[java]  view plain  copy
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. import java.util.Random;  
  4.   
  5. public class ThreadScopeShareData {  
  6.   
  7.     private static int data = 0;    //定义一个全局的成员变量  
  8.     private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();  
  9.     public static void main(String[] args) {  
  10.         //启动两个线程  
  11.         for(int i=0;i<2;i++){  
  12.             new Thread(new Runnable(){  
  13.                 @Override  
  14.                 public void run() {  
  15.                     int data = new Random().nextInt();  //准备一个数据  
  16.                     System.out.println(Thread.currentThread().getName()   
  17.                             + " has put data :" + data);  
  18.                     //把当前线程对象作为键,就可以保证map对象的唯一,即保证线程内的数据唯一  
  19.                     threadData.put(Thread.currentThread(), data);  
  20.                     new A().get();  
  21.                     new B().get();  
  22.                 }  
  23.             }).start();  
  24.         }  
  25.     }  
  26.     //定义一个类模拟获取数据  
  27.     static class A{  
  28.         public void get(){  
  29.             int data = threadData.get(Thread.currentThread());  
  30.             System.out.println("A from " + Thread.currentThread().getName()   
  31.                     + " get data :" + data);  
  32.         }  
  33.     }  
  34.       
  35.     static class B{  
  36.         public void get(){  
  37.             int data = threadData.get(Thread.currentThread());            
  38.             System.out.println("B from " + Thread.currentThread().getName()   
  39.                     + " get data :" + data);  
  40.         }         
  41.     }  
  42. }  

打印结果

Thread-0 has put data:-49248136

Thread-1 has put data:311124475

A from Thread-0 get data:-49248136

A from Thread-1 get data:311124475

B from Thread-0 get data:-49248136

B from Thread-1 get data:311124475


二、ThreadLocal类

 

1、ThreadLocal的作用和目的:

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

 

2、 每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方

      法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放

      相关的ThreadLocal变量。

 


三、ThreadLocal的应用场景

 

1、订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,

      如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作

      的代码分别位于不同的模块类中。


2、 银行转账包含一系列操作:把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连

        接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。


3、例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方

       法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。


线程范围内共享数据示意图

 



 

四、实现对ThreadLocal变量的封装, 让外界不要直接操作ThreadLocal变量

      由于对基本类型的数据的封装,这种应用相对很少见。而对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。
     所以我们要对数据进行封装。


实现方式一

示例说明:

       1、 该示例包含了对基本类型数据的共享和对象类型数据的共享

       2、定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类

            的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。

       3、但这里每次存储数据时,都是使用同一个ThreadLocal对象,只是重新赋值而已

[java]  view plain  copy
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. import java.util.Random;  
  4.   
  5. public class ThreadLocalTest {  
  6.       
  7.     private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();  
  8.     //创建一个存储封装类对象的ThreadLocal  
  9.     private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new  ThreadLocal<MyThreadScopeData>();  
  10.     private static int data = 0;  
  11.       
  12.     public static void main(String[] args){  
  13.       
  14.         //产生两个线程  
  15.         for(int i=0;i<2;i++){  
  16.         new Thread(new Runnable(){  
  17.   
  18.         @Override  
  19.         public void run() {  
  20.             //共享单一的数据  
  21.             int data = new Random().nextInt();  
  22.             System.out.println(Thread.currentThread().getName()+"has put data : "+data);  
  23.             x.set(data);      
  24.               
  25.             //共享多个数据  
  26.             //将数据封装在myData对象中,并将myData作为myThreadScopeData的键  
  27.             MyThreadScopeData myData = new MyThreadScopeData();  
  28.             myData.setName("name "+data);  
  29.             myData.setAge(data);  
  30.             myThreadScopeData.set(myData);  
  31.               
  32.             new A().get();  
  33.             new B().get();  
  34.             }  
  35.          }).start();  
  36.       }  
  37.    }  
  38.       
  39.     static class A{  
  40.         public void get(){  
  41.             int data = x.get();  
  42.             System.out.println("A from "+Thread.currentThread().getName()+" get data :"+data);  
  43.           
  44.             //从myData中取出数据,并获取当前线程名,数据  
  45.             MyThreadScopeData myData = myThreadScopeData.get();  
  46.             System.out.println("A from "+Thread.currentThread().getName()+" getMyData: " +   
  47.                     myData.getName() + "," +myData.getAge());  
  48.         }  
  49.     }  
  50.           
  51.     static class B{  
  52.         public void get(){  
  53.             int data = x.get();  
  54.             System.out.println("B from "+Thread.currentThread().getName()+" get data :"+data);  
  55.             MyThreadScopeData myData = myThreadScopeData.get();  
  56.             System.out.println("B from "+Thread.currentThread().getName()+" getMyData: " +   
  57.                     myData.getName() + "," +myData.getAge());  
  58.         }  
  59.     }  
  60. }  
  61.   
  62. //封装数据的类  
  63. class MyThreadScopeData{  
  64.       
  65.     private String name;  
  66.     private int age;  
  67.     public String getName() {  
  68.         return name;  
  69.     }  
  70.     public void setName(String name) {  
  71.         this.name = name;  
  72.     }  
  73.     public int getAge() {  
  74.         return age;  
  75.     }  
  76.     public void setAge(int age) {  
  77.         this.age = age;  
  78.     }  
  79. }  



实现方式二

     

 示例说明:

这里模拟原始的单例模式,它们的区别是:单例模式中只有唯一的一个实例,而这里是每个线程拥有

    自己唯一的实例,只要是已经创建,就直接返回,保证每个线程拥有自己的唯一一份实例


优点:

     这里可以返回每个线程自己唯一的实例对象,所以不必在外面定义,当在代码中的任意地方想获取到一个可以存储自己数据的线程实例的时候直接去调用

     getThreadInstance方法即可,直接定义在数据对象的内部,和数据关系更紧密,而方式一,则每次想存入数据的时候都需要在外面创建一个ThreadLocal对象用

    于存储数据。所以方式二更具封装性。

[java]  view plain  copy
  1. package cn.itcast.heima2;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import java.util.Random;  
  6.   
  7. public class ThreadLocalTest {  
  8.       
  9.     //创建一个ThreadLocal对象  
  10.     private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();  
  11.       
  12.     public static void main(String[] args) {  
  13.         for(int i=0;i<2;i++){  
  14.             new Thread(new Runnable(){  
  15.                 @Override  
  16.                 public void run() {  
  17.                     int data = new Random().nextInt();  
  18.                     System.out.println(Thread.currentThread().getName()   
  19.                             + " has put data :" + data);  
  20.                     x.set(data);    //往当前线程存入一条数据  
  21.                       
  22.                     //获取与当前线程绑定的实例并设置值  
  23.                     MyThreadScopeData.getThreadInstance().setName("name:" + data);  
  24.                     MyThreadScopeData.getThreadInstance().setAge(data);  
  25.                     new A().get();  
  26.                     new B().get();  
  27.                 }  
  28.             }).start();  
  29.         }  
  30.     }  
  31.       
  32.     static class A{  
  33.         public void get(){  
  34.             int data = x.get();     //获取当前线程中的数据  
  35.             System.out.println("A from " + Thread.currentThread().getName()   
  36.                     + " get data :" + data);  
  37.               
  38.             //获取与当前线程绑定的实例  
  39.             MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();  
  40.             System.out.println("A from " + Thread.currentThread().getName()   
  41.                     + " getMyData: " + myData.getName() + "," +  
  42.                     myData.getAge());  
  43.         }  
  44.     }  
  45.       
  46.     static class B{  
  47.         public void get(){  
  48.             int data = x.get();           
  49.             System.out.println("B from " + Thread.currentThread().getName()   
  50.                     + " get data :" + data);  
  51.             MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();  
  52.             System.out.println("B from " + Thread.currentThread().getName()   
  53.                     + " getMyData: " + myData.getName() + ",age: " +  
  54.                     myData.getAge());             
  55.         }         
  56.     }  
  57. }  
  58.   
  59. //一个绑定当前线程的类  
  60. class MyThreadScopeData{  
  61.   
  62.     private MyThreadScopeData(){}   //构造方法私有化  
  63.     private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();  
  64.       
  65.     //定义一个静态方法,返回各线程自己的实例   
  66.     //这里不必用同步,因为每个线程都要创建自己的实例,所以没有线程安全问题。  
  67.     public static /*synchronized*/ MyThreadScopeData getThreadInstance(){  
  68.         MyThreadScopeData instance = map.get();     //获取当前线程绑定的实例  
  69.         if(instance == null){         
  70.             instance = new MyThreadScopeData();  
  71.             map.set(instance);  //创建完之后,将实例对象存进去  
  72.         }  
  73.         return instance;  
  74.     }  
  75.   
  76.     private String name;  
  77.     private int age;  
  78.     public String getName() {  
  79.         return name;  
  80.     }  
  81.     public void setName(String name) {  
  82.         this.name = name;  
  83.     }  
  84.     public int getAge() {  
  85.         return age;  
  86.     }  
  87.     public void setAge(int age) {  
  88.         this.age = age;  
  89.     }  
  90. }  

总结:

       一个ThreadLocal代表一个变量,故其中只能放一个数据,有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象,如果数据更多

      就很麻烦,可以先定义一个对象封装变量,然后在ThreadLocal中存储这一个对象,而这些操作都在提供线程数据类中完成。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!下面是一篇关于Java多线程中的"锁"的博客: ## Java多线程中的锁 在Java多线程编程中,锁是一种重要的同步机制,用于保护共享资源的访问。使用锁可以防止多个线程同时对共享资源进行修改,从而避免数据不一致的问题。 ### 1. 什么是锁? 锁是一种同步机制,它可以让多个线程按照一定的顺序访问共享资源。在Java中,锁可以是隐式的,也可以是显式的。 - 隐式锁:通过关键字`synchronized`来实现,它可以用于修饰方法或代码块。当一个线程进入被`synchronized`修饰的方法或代码块时,它会自动获取锁,并在执行完毕后释放锁。 - 显式锁:通过`java.util.concurrent.locks`包中的Lock接口及其实现类来实现,如ReentrantLock。显式锁需要手动获取和释放,在使用上更加灵活。 ### 2. 锁的作用 锁的主要作用是保护共享资源的访问,它可以解决多线程并发访问时可能引发的以下问题: - 竞态条件(Race Condition):当多个线程同时访问共享资源,并且对其进行写操作时,可能会导致数据的不一致。 - 临界区(Critical Section):当多个线程同时访问共享资源,并且对其进行读写操作时,可能会导致数据的不一致。 - 死锁(Deadlock):当多个线程相互等待对方释放锁时,可能会导致程序无法继续执行。 ### 3. 锁的类型 Java中常用的锁类型包括: - 内置锁(Intrinsic Lock):也称为监视器锁(Monitor Lock),是由关键字`synchronized`来实现的。内置锁是基于对象的,每个对象都有一个用于同步的内置锁,当一个线程获取了该锁后,其他线程必须等待。 - 重入锁(Reentrant Lock):是`java.util.concurrent.locks`包中的一个显式锁实现类,它具有与内置锁类似的功能,但提供了更高级的特性,如可重入、公平和超时等。 - 读写锁(Read-Write Lock):也是`java.util.concurrent.locks`包中的一个显式锁实现类,它区分了读操作和写操作,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。 ### 4. 锁的使用示例 下面是一个使用内置锁`synchronized`来实现线程安全的示例: ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 在上述示例中,`increment()`和`getCount()`方法都被`synchronized`修饰,这意味着同一时间只能有一个线程执行这些方法,从而保证了`count`变量的访问安全。 ### 5. 锁的注意事项 在使用锁时,需要注意以下事项: - 避免死锁:在获取锁的时候,要确保能够及时释放锁,避免多个线程相互等待对方释放锁而导致死锁。 - 避免饥饿:要确保所有线程都有公平获取锁的机会,避免某个线程一直无法获得锁而导致饥饿。 - 锁的粒度:要选择合适的锁粒度,尽量减小锁的范围,以提高程序的并发性能。 - 锁的性能:显式锁相对于内置锁,通常具有更高的性能,但使用不当可能导致性能问题。要根据实际情况选择合适的锁。 ### 总结 锁是Java多线程编程中重要的同步机制,用于保护共享资源的访问。它可以解决竞态条件、临界区和死锁等问题。在使用锁时,需要注意避免死锁和饥饿,选择合适的锁粒度和锁类型,以及权衡锁的性能。 希望本篇博客对你有所帮助!如有任何问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值