summary

2018年04月09日

ThreadLocal: 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁的编写出优美的多线程徐程序,ThreadLocal 并不是一个Thread,而是Thread的局部变量,把它命名为ThreadLocalVariable更容易让人理解一些。

ThreadLocal并不是用来并发控制访问一个共同对象,而是为了给每个线程分配一个只属于该线程的变量。他的 功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本, 而不会和其他线程的副本冲突,实现线程间的数据隔离。从线程的角度看,就好像每一个线程都完全拥有该变量

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时 能保证每个线程里的变量相对独立于其他线程的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文

引入ThreadLocal的初衷是为了提供线程内的局部变量,而不是为了解决共享对象的多县城访问问题,实际上,ThreadLocal根本就不能解决共享 对象的多线程访问问题

这些解释都没错,但并不是很好理解,而且大多数读者也没有真正使用过ThreadLocal,字面意思看上去 理解了,真正盗用的时候又不知从何下手。 从演化、实现和使用场景三个方面来进行ThreadLocal的解释:

1.演化过程

以实际生活中的银行业务办理模型,解释ThreadLocal的诞生过程,读者们可以看到:随着业务模型的 不断扩展,代码逻辑变得更加复杂,通过不断优化代码结构的过程,演化出了ThreadLocal这个 编程工具。

1.1初始形态 大家去银行办理业务时,如果需要排队等候,则会领取一个排队号,直到叫号才能办理业务。 我们把每一笔业务抽象为一个线程,每一笔业务都有一个唯一的标识:

class Transaction extends Thread{
   private int id;
   
   public void run(){
      if (wait){
      }
      else {
      
      }
   }
}

这个模型中,每新来一笔业务,都需要运行一个线程,然后分配一个全局唯一的业务标识(id)给 这个新的线程,简化以后的代码逻辑如下:

int id = nextTransactionId();
new Transaction(id).start();

1.2扩展形态

现在,需要把业务模型扩展一下,每一笔业务还需要知道等待时间(waitTime),等待人数 (waitPeople)等。 于是乎,在原有的线程里面,又增加了一些局部变量和控制逻辑,线程运行以后便会对这些局部变量进行 读写操作。

class Transaction extends Thread{
    private int id;
    private long waitTime;
    private long waitPeople;
    private int serviceWindow;
    
    public void run(){
       if (wait){
          waitTime increasing
          }
       else{
       serviceWindow assigned
       waitPeople decreasing
       }
       }
       }

添加完扩展代码逻辑之后,我们发现这种编程办法并不好:譬如,要扩展一个业务办理时长, 又得新增一个局部变量。可以想象,类似的扩展还有很多。于是乎,我们想到吧这些零散的字段封装成一个类, 这里我们命名为Session,表示一个事务所需要操作的数据集合:

 class  Transaction extends Thread{
    private Session session;
    
    public void run(){
       if (wait){
       }
       }
       else {
         //read write session
       }
 }
   
 class Session {
   private int id;
   private long waitTIme;
   private long waitPeople;
   private int serviceWindow;
   
 }

这样一来,每个线程都拥有一个局部变量Session,后续可以在Session的基础上进行扩展, 降低Transaction的复杂度,当线程运行时,需要对Session对象进行读写。 注意,银行的业务室多窗口同时办理的,意味着这些线程可以并发执行。一闪代码并没有锁控制, 因为每个线程都是修改自己的局部变量,并不影响其他线程。

随着银行的业务变得愈加复杂,譬如:客户可以买卖理财产品,缴纳日常生活费用。 Transaction 的代码量变得越来越大,于是乎,又把与理财业务相关的代码封装到FinacialService。

class Transaction extends Thread{
   private Session session;
   
   public void run(){
   if (wait){
      //read write session
      }
      else {
        /read write session;
      }
      }
}

class Session{
   private  int id;
   private long waitTime;
   private long waitPeople;
   private int serviceWindow;
   private long serviceTime;
   
}

class FinancialService{
   Session session;
   
   public void setSession(Session session){
      this.session =session;
   }
   public void doService(){
   //read write session
   }
}
}

扩展出来的FinancialService需要读写Session中的数据,譬如:获取分配的服务窗口(serviceWindow)、更新服务 时间(serviceTime),所以,在FinancialService类中也会有一个局部变量Session,它是外部传入进来的。

可以这么来理解:FinancialService属于一个具体的事务,FinancialService对象依然属于Transaction这个 线程的生命周期,在Transaction线程的生命周期内,需要将Session对象传入FinancialService对象。

1.3改良形态

Transaction线程的代码逻辑已经很复杂了,涉及到很多类的封装和数据传递,在线程运行时,有一些变量在
整个线程的生命都存在的,如果线程中某些对象需要使用这些变量,就需要封装一些借口进行数据传递。有没有一种便捷的方式来访问这些变量呢
在Transaction中创建一个Map类型的局部变量,通过一个全局可以访问的key,便可对Session进行存取操作。在
线程生命周期的任何地方,只需要通过key,就可以获取到Session

static SessionKey globalKey  =  new SessionKey();

class Transaction extends Thread{
   Map<SessionKey,Session> map;
   
   public void run(){
       map.put(globalKey,session):
       if (wait){
       }
       else {
       }
   }
}

class FinancialService{
   public void doService(){
     Thread t = Thread.currentThread():
     Session session = t.map.get(globalSessionKey);
     }
     }

注意,此处两个关键点: 全局变量Key,所有线程都可以访问 局部变量Map,属于每个线程,这个Map中每一项的Key是全局的,而Value是局部的

线程类Transaction中定义了一个类型为Map的变量,其中每一项的key为SessionKey,Value为Session。读者一定心生疑问了,直接 将Session作为全局变量不就可以了吗?为什么还要搞一个线程的局部变量Map 这就涉及到多线程数据访问了:对于Session而言,每个线程都各自维护自己的,修改了也不需要告诉其他线程。如果将Session直接作为 全局变量,那每个线程都改的是同一份数据,还需要进行多线程的锁的控制。 演化到这一步,ThreadLocal就呼之欲出了。

2.实现原理

先直接把Thread与ThreadLocal之间的关系图表示出来:

这个结构图跟上面改良形态的Transaction结构图简直如出一辙,只不过ThreadLocal做了更多的封装: 线程类Thread中有一个类型为ThreadLocalMap的变量为threadLocals ThreadLocalMap是一个映射表,内部是海鲜是一个数组,每一个元素的类型为Entry Entry就是一个键值对(key-Value pair),其key就是ThreadLocal,其value可以是任何对象 接下来,我们深入源码,窥探一下ThreadLocal的奥妙。

2.1ThreadLocal的主要接口:

JDK1.8以前,仅set(),get()和remove()三个接口;JDK1.8以来,多提供了一个withInitial()接口,这些接口其实就是针对线程中 ThreadLocalMap的增删改查操作。 set(),表示设置本地变量 get(),表示从当前线程中取出”本地变量“,最终的结果是在当前线程的映射表中,以调用 get()方法的ThreadLocal对象为key,查询出对应的Value

2.2ThreadLocalMap映射表

ThreadLocal并不是一个存储容器,往ThreadLocal中读写数据,其实都是将数据保存到了每个线程自己的存储及空间。 线程中的存储空间是一个映射表,ThreadLocal其实就是这个映射表每一项的Key,通过ThreadLocal读写数据,其实就是通过Key 在一个映射表中读写数据,

3.应用场景: 线程通过THreadLocal提供的接口来操作自己内部的映射表,可以这么理解:线程把ThreadLocal当做自己的局部变量,不过对这个变量的赋值 操作是set(),读取操作是get(),清空操作是remove()

3.1android looper

handler将线程抛送到线程的消息对垒。控制消息队列的类是Looper,每个用友消息队列的线程,都有一个独立的Looper类,用于处理本县城的消息。一种实现方式是, 在线程类中,声明一个Looper类型的局部变量,当线程运行起来时,创建Looper对象,并开始进行无限循环,代码示意如下:

public class LooperThread extends Thread{ } 3.2 Android SQLiteDatabase

3.3 总结: 通过上述使用场景发现,ThreadLocal确实提供了一种编程手段,本来需要在线程中显示声明的局部变量,像是被ThradLocal隐藏了 起来,当多个线程运行起来时,每个线程都往相同的ThreadLocal中存取所需要的变量就可以了,使用ThreadLocal存取的变量,就像是每个 线程自己的局部变量,不收其他线程运行状态的影响。

通过ThreadLocal可以解决多线程读贡献该数据的问题,因为共享数据会被复制到每个线程,不需要加锁便可同步访问,但THreadLocal解决不了 多线程写共享数据的问题,因为每个线程写的都是自己本线程 的局部比阿娘,并没将写数据的结果同步到其他线程,理解了这一点,才能理解 所谓的: ThreadLocal以空间换时间,提升多线程并发的效率,什么意思呢?每个线程偶有一个ThreadLocalMap映射表,正是 利用了这个映射表所占用的空间,使得多个线程都可以访问自己的这片空间,不用担心考虑线程同步问题,效率自然会搞 ThreadLocal并不是为了解决共享数据的互斥写问题,而是通过一种编程手段,正好提高了并行读的功能。什么意思呢?ThreadLocal并不是 万能的,他的设计初衷只是提供一个便利性,使得线程可以更为方便的使用局部变量。 ThreadLocla提供了一种线程全域访问功能,一旦将一个对象添加到ThreadLocal中,只要不移除它,那么,在线程的生命周期内的 任何地方,都可以通过ThreadLocal.get()方法拿到这个对象,有时候,代码逻辑比较复杂,一个线程的代码可能分散在很多地方, 利用ThreadLocal这种便利性,就能简化编程逻辑。

1.tcp三次握手 2.request到SpringMVC的过程 3.mysql的行级锁 4.redis的数据类型:string,hash,list,set,zset 5.中间件技术 6.nginx包含哪些核心模块 7.负载均衡 8.mysql分库分表 9.大数据hadoop的框架和生态
10.ConcurrentHashMap技术

1.jvm调优,内存结构

2.内存碎片问题 MajorGC是清理老年代 MinorGC是从年轻代空间(包括Eden和Survivor区域)回收内存。 FullGC是清理整个堆空间包括年轻代和老年代。

3.mysql执行计划,查询慢 MySQL查询优化: 查询性能衡量标准 查询时间 慢查询(几乎所有大道秒级别的查询都可以认为比较慢) 执行计划 type查询的方式 key使用的索引 Rows结果集大小 Extra提示信息 影响查询性能的因素 SQL解析时间#使用prepared stmt语句减少重复SQL的解析 查询优化算法 执行计划分析 索引优化 表优化(建主键unsigned int) 表优化(字段尽量使用NOT NULL) 表优化(能使用enum的尽量不要使用varchar) 表优化(ip字段使用unsigned int并使用INET_NTOA和INET_ATON) 索引优化(为频繁搜索的字段建立索引) 索引优化(为varchar text建立全文索引,避免使用 like) 索引优化(避免使用blob字段,该字段只能建立前缀索引) 索引优化(最多匹配原则和最高区分度原则) 查询优化 索引字段不参与计算,否则索引失效 避免使用 select,select count()等 知道结果数量的使用,使用limit,尽早结果过程。 Query Cache的使用#关了吧 磁盘IO次数#IO优化,增大buffer pool,开取MRR,避免select * 事务键锁的影响#锁优化:避免使用大事务,RC比RR好,不适用GAP锁,避免使用select *,可能会锁权标 join优化#利用index nexted-loop join算法,在没有索引的情况下,合理设置join_buffer_size. 走索引OR全表扫描 结果集大小与运算过程

查询计划分析

查询优化技巧

MySQL执行计划: 语法: EXPLAIN select … 变体: 1.将执行计划“反翻译”成SELECT 语句,运行show warnings 可得到MySQL优化器优化后的查询语句 EXPLAIN EXTENDED select … 2.用于分区表的EXPLAIN EXPLAIN PARTITIONS SELECT … 二。执行计划包含的信息 id:包含一组数字,标识查询语句中执行SELECT子句或者操作表的顺序。id相同,执行顺序由上至下。若是子查询,id序号递增,id值越大优先级越高,越会被执行 select_type:表示每个select子句的类型(简单或复杂) SIMPLE:查询中不包含子查询或者UNION PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为该类型 SUBQUERY:在SELECT或where列表中包含了子查询,该子查询就被标记为SUBQUERY DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生) UNION:若第二个SELECT出现在UNION之后,则被标记为UNION UNION RESULT:从UNION表获取结果的SELECT被标记为UNION RESULT type:标识MySQL在表中找到所需行的方式,又称为“访问类型” 这列很重要,显示了连接使用了哪种类别,有无使用索引.

从最好到最差的连接类型为const、eq_reg、ref、range、index和AL mysql explain用法和结果的含义:https://www.cnblogs.com/yycc/p/7338894.html MYSQL 5.6.3以前只能EXPLAIN SELECT; MYSQL5.6.3以后就可以EXPLAIN SELECT,UPDATE,DELETE

不走索引情况

select * tb1 where name like ‘%comnn’ %再在前不走索引

select * tb1 where reverse(name) = ‘xu’ 条件出现函数运算不走索引

使用 or 不走索引 特别的: 当or条件中有 未 建立索引的列,会走索引

数据类型不一致 select * tb1 where name = 0999

!= 特别的: 如果是主键 索引生效

> 特别的: 如果是主键 或者索引是int类型 索引生效

order by

选择的映射是索引才生效 特别的: 如果是主键 索引生效

4.GMS回收器: 5.spring AOP中的两种代理。 6.HashMap中出现冲突时如何解决。 7.Java程序占用高内存,如何找出问题及调优 8.Java中的代理,Spring AOP原理为什么用2中实现方式?JDKProxy和cglib。 JDK的动态代理依靠接口实现,如果这些类并没有实现接口,则不能使用JDK代理。这个时候就需要使用Cglib在字节码上做代理。 其实还是JDK本身的局限性导致的。 反射是java的最重要的底层知识。而动态代理其实质就是依靠反射来实现的。 cglib到哪个台代理代理的是类,不需要业务类继承接口,通过派生的子类来实现代理,通过在运行时,动态修改字节码达到修改类的目的。 cglib采用的是继承,不能对final修饰的类进行代理。

java中为什么接口中的变量一定的是static和final类型? static final修饰的不可变的变量,是常量。 final表示终态,不可变,不可修改。 static表示在类加载到内存的时候就创建。 static final修饰的常量,一般用于接口或者数据库连接中,为了就是全局可用,不管哪个包,哪个类都可直接访问,二是一般服务器启动,类加载到内存便无法修改,就拿数据库连接来讲,就确定参数不可动,保证其他程序的修改不会影响数据库连接。

9.线程池的管理 newSingleThreadExecutor:创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行 newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,知道线程大道线程池的最大大小。线程池的大小一旦大道最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新县城。 newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能创建的最大线程大小。 newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 不可重入锁也叫自旋锁。 java中常用的可重入锁: synchronized java.util.concurrent.locks.ReentrantLock

专利撰写要点: xxxx1 xxxx2

参考

http://duanqz.github.io/2018-03-15-Java-ThreadLocal#3-%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF


相关内容已同步到justpic公众号

期待您的分享与讨论: