0%

ThreadLocal是什么

首先我们要明白一点,线程同步主要是为了完成线程间数据共享和同步,保持数据的完整性。而ThreadLocal正如他的名字一样是线程的本地变量,也就是线程所私有的。因此他和线程同步无关,(因为不存在线程之间共享变量的问题,就不需要使用同步机制)。ThreadLocal虽然提供了一种解决多线程环境下成员变量的问题,但是它并不是解决多线程共享变量的问题。那么ThreadLocal到底是什么呢?

官方文档是这样介绍它的:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).

中文翻译

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get 或 set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。正如这句话所说,spring 中的事务就使用了ThreadLocal来保证同一个线程中的所有操作使用的是同一个数据库连接。

所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。

如何使用ThreadLocal

简单案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class SeqCount {

private static ThreadLocal<Integer> seqCount = new ThreadLocal<Integer>(){
// 实现initialValue()
public Integer initialValue() {
return 0;
}
};

public int nextSeq(){
seqCount.set(seqCount.get() + 1);

return seqCount.get();
}

public static void main(String[] args){
SeqCount seqCount = new SeqCount();

SeqThread thread1 = new SeqThread(seqCount);
SeqThread thread2 = new SeqThread(seqCount);
SeqThread thread3 = new SeqThread(seqCount);
SeqThread thread4 = new SeqThread(seqCount);

thread1.start();
thread2.start();
thread3.start();
thread4.start();
}

private static class SeqThread extends Thread{
private SeqCount seqCount;

SeqThread(SeqCount seqCount){
this.seqCount = seqCount;
}

public void run() {
for(int i = 0 ; i < 3 ; i++){
System.out.println(Thread.currentThread().getName()
+ " seqCount :" + seqCount.nextSeq());
}
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
Thread-1 seqCount :1
Thread-0 seqCount :1
Thread-2 seqCount :1
Thread-3 seqCount :1
Thread-1 seqCount :2
Thread-0 seqCount :2
Thread-2 seqCount :2
Thread-3 seqCount :2
Thread-1 seqCount :3
Thread-0 seqCount :3
Thread-2 seqCount :3
Thread-3 seqCount :3

从运行结果可以看出,ThreadLocal确实是可以达到线程隔离机制,确保变量的安全性。

不过这里要注意一个问题,不要使用对象的引用来设置ThreadLocal的初始值,这样会导致线程共享此对象

就像下面这样写

1
2
3
4
5
6
7
8
9
10
11
A a = new A();
private static ThreadLocal<A> seqCount = new ThreadLocal<A>(){
// 实现initialValue()
public A initialValue() {
return a;
}
};

class A{
// ....
}

具体过程请参考:对ThreadLocal实现原理的一点思考

总结一般使用ThreadLocal的方式是:

  1. 设置ThreadLocal对象为静态内部私有属性
  2. 使用initalValue方法进行初始化值的时候,不要使用引用对象,这样会造成线程间共享此对象,从而达不到线程私有变量的的效果。

具体代码如下

1
2
3
4
5
6
7
private statis ThreadLocal<T> objectName = new  ThreadLocal(){
private T initialValue() {
//下面写法肯定有问题,主要是告诉你要声明一个对
//象,而不是返回对象的引用
return new T() ;
}
}

ThreadLocal源码解析

ThreadLocal定义了四个方法:

  • get():返回当前线程对应的ThreadLocal中的值。
  • initialValue():用于初始化当前线程对应的ThreadLocal的“初始值”。
  • remove():删除当前线程对应的ThreadLocal值。
  • set(T value):设置当前线程对应的ThreadLocal中的值。

除了这四个方法,ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。

对于ThreadLocal需要注意的有两点:

  1. ThreadLocal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key。
  2. 是ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal中,有些小伙伴会弄错他们的关系。

下图是Thread、ThreadLocal、ThreadLocalMap的关系

upload successful

ThreadLocal虽然解决了这个多线程变量的复杂问题,但是它的源码实现却是比较简单的。ThreadLocalMap是实现ThreadLocal的关键,我们先从它入手。

阅读全文 »

  1. 线程池的创建
  2. ThreadFactory:线程创建工厂
  3. RejectedExecutionHandler:任务拒绝策略

线程池官方文档

下面是对线程池注释的一些翻译以及自己的理解,首先为什么使用线程池,线程池能够对线程进行统一分配,调优和监控,有以下有点

  1. 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
  2. 提高响应速度(无须创建线程)
  3. 提高线程的可管理性

通过以下措施可以优化和配置线程池

corePoolSize和maximumPoolSize

线程池可以根据corePoolSize和maximumPoolSize来自动调整线程池线程的数量

  1. 当线程池小于corePoolSize时,会一直创建线程来执行提交的任务,即使其它线程是空闲状态
  2. 如果线程数量大于corePoolSize小于maximumPoolSize,只会在队列已满的情况下创建线程。

默认情况下,corePoolSize和maximumPoolSize是也一样的,也就是会创建一个固定大小的线程数量的线程池,如果maximumPoolSize的大小是无限的,则在队列已满的情况下,会一直创建新线程。

提前初始化线程

默认情况下,核心线程只会在任务到达时才会创建,这个可以调用prestartCoreThread和prestartAllCoreThreads来提前初始化核心线程,或者使用一个非空的队列来初始化

创建新线程

线程的创建调用ThreadFactory来进行创建,如果没有指定,则会调用Executors#defaultThreadFactory来进行创建,创建出来的线程在相同的ThreadGroup,NORM_PRIORITY,以及非daemon状态。同时也可以自定义一个新的ThreadFactory,来改变这些值。同时应该保证创建出来的线程是可运行的,如果不可运行会导致线程池的服务能力下降。

存活时间

线程池的线程如果超过了corePoolSize设置的大小,并且有一些线程是空闲的,则可以通过设置keepAliveTime,来回收这些空闲线程,从而降低资源的消耗。默认情况下,keepAliveTime只会用来回来超过核心线程数量的线程,但是也可以通过allowCoreThreadTimeOut方法,来回收核心线程。

队列

BlockingQueue可以用来传递和保存提交的任务,队列与线程池的交互是通过队列的长度来决定的,主要有以下情况

  1. 如果线程池中的线程数量小于corePoolSize,则会一直创建线程
  2. 如果大于corePoolSize,则会先将任务放进队列中
  3. 如果任务不能入队,则会创建一个新的线程执行任务,如果超过了maximumPoolSize,则会执行对应的拒绝策略。

入队对应着若干个策略

  1. 直接传递(Direct handoffs):使用一个类似SynchronousQueue的队列,不会存储任务,而是直接将任务交给线程执行。前提是maximumPoolSizes无限大,不会导致线程创建失败从而执行拒绝策略
  2. 无界队列(Unbounded queues):使用一个类似LinkedBlockingQueue的队列,所有的任务首先会被入队,这个比较适合后台任务,不需要获取结果的情形
  3. 有界队列(Bounded queues):使用类似ArrayBlockingQueue的队列,可以阻止资源的消耗,当任务超过队列的长度,以及线程超过maximumPoolSizes的长度,将会直接决绝任务。

拒绝策略

下面会介绍,就不在具体说明

钩子方法

线程池提供了一些默认的方法,可以用于任务执行前或者执行后进行处理

  • beforeExecute:任务执行前调用的方法
  • afterExecute:任务执行后调用的方法
阅读全文 »

ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何时候访问这个本地变量的结果都是一致的。当此线程结束生命周期时,所有的线程本地实例都会被GC。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。ThreadLocal通常定义为private static类型。

阅读全文 »

在java的多线程中有线程组ThreadGroup的概念,ThreadGroup是为了方便线程管理而出现,可以统一设定线程组的一些属性,比如setDaemon,设置未处理异常的处理方法,设置统一的安全策略等等;也可以通过线程组方便的获得线程的一些信息。

每一个ThreadGroup都可以包含一组的子线程和一组子线程组,线程组是以树形的方式存在,通常情况下根线程组是system线程组。system线程组下是main线程组,默认情况下第一级应用自己的线程组是通过main线程组创建出来的。

阅读全文 »

java中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。

java中有两种线程:用户线程守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。

阅读全文 »

在介绍终止线程的方式之前,有必要先对interrupt()进行了解。
关于interrupt(),java的djk文档描述如下:http://docs.oracle.com/javase/7/docs/api/

1
2
3
4
5
6
7
8
9
10
11
Interrupts this thread.Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an interruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.

大致意思是:

1
2
3
4
5
interrupt()的作用是中断本线程。
本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。中断一个“已终止的线程”不会产生任何操作。
阅读全文 »

本文首先对线程的等待和唤醒概念进行简单介绍,接着介绍如何使用,并缝隙为什么这俩个功能是放在Object类中,而不是放置在Thread线程里,最后介绍下线程休眠,以及其和wait的区别。

线程等待和唤醒方法介绍

在Object类中定义了wait(), notify()和notifyAll()三个方法用于等待和唤醒线程。

wait()的作用是让当前线程进入等待状态,同时wait()也会让当前线程释放它所持有的锁
而notify()和notifyAll()的作用则是唤醒当前对象上的等待线程notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

Object类中关于等待/唤醒的API详细信息如下:

  • notify() :唤醒在此对象监视器上等待的单个线程。
  • **notifyAll()**:唤醒在此对象监视器上等待的所有线程。
  • wait() :让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()方法或 notifyAll()方法,当前线程被唤醒(进入就绪状态)。
  • **wait(long timeout)**:让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify()方法或notifyAll()方法,或者超过指定的时间量,当前线程被唤醒(进入就绪状态)。
  • wait(long timeout, int nanos) – 让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量,当前线程被唤醒(进入就绪状态)。
阅读全文 »