Java基础07-多线程

线程状态

      

  1. 同一个进程中的线程,共享其进程中的内存和资源(共享内存指堆内存和方法区内存,栈内存不共享)。每个线程有私有的运行栈和程序计数器。
  2. 一个CPU 在任何时刻只会被一个线程所有使用。CPU 采取为每个线程分配时间片并轮转的形式,让线程都能得到有效执行。
  3. 当前任务在执行完CPU 时间片切换到另一个任务之前,会先保存自己的状态,以便在下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换过程。
  4. 方法比较
    1. Thread.sleep(long time),一定是当前线程调用此方法,使当前线程阻塞,放弃cpu 时间片,但不释放对象锁,time后线程自动苏醒进入可运行状态。作用是给其它线程执行机会;
    2. Thread.yield(),一定是当前线程调用此方法,使当前线程放弃的cpu时间片,由运行状态变为可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。其不会导致阻塞。
    3. obj.wait(),当前线程调用对象的wait方法,它就放弃cpu 时间片,进入到一个和该对象相关的等待队列,同时当前线程释放对象锁,使得其他线程能够访问。依靠notify/notifyAll方法唤醒或者wait(long time)time时间到自动唤醒,进入到锁池队列;
    4. t.join()/t.join(long time),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或time时间到,当前线程进入可运行状态。
线程池
  1. 线程池是提供了一种限制和管理资源(包括执行一个任务)的方式。
  2. 优点:
    1. 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
    2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    3. 提高线程的可管控性。可以统一管理线程的创建,统一的分配,调优和监控。

Executor 框架

      

  • Executor 框架不仅包括线程池的管理,还提供了线程工厂、队列以及拒绝策略等。Executor 框架结构由三部分组成:

       

  • 创建线程(Thread/Runnable/Callable)所有创建线程的方式归结起来,都是通过创建Thread 实例来实现。
  • 继承Thread 类并调用start() 方法就可创建线程;
class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • 实现Runnable 接口并创建一个Thread 对象,将实现类对象作为Thread构造方法入参。
class RunnableTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class CreateThreadTest {
    public static void main(String[] args) {
        RunnableTest runnableTest = new RunnableTest();
        Thread thread = new Thread(runnableTest);
        // new Thread(() -> System.out.println(Thread.currentThread().getName()));
        thread.start();
    }
}
  • 实现Callable 接口,同时需结合线程池的submit()方法才能使用。与Runnable 本质区别是Callable 的call()方法可返回值。
class CallableTest implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        return ThreadLocalRandom.current().nextInt();
    }
}
public class CreateThreadTest {
    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future<Integer> future = executorService.submit(callableTest);
    }
}
  1. 任务的执行(Executor)
    1. 包括任务执行机制的核心接口 Executor ,以及继承自 Exectuor 接口的 ExecutorService 接口;实现了 ExecutorService 接口的实现类 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 类。
  2. 异步计算的结果(Future/FutureTask)
    1. Future 表示一个异步计算的结果。它提供了isDone() 来检测计算是否已经完成,并且在计算结束后,可通过get() 方法来获取计算结果。
    2. FutureTask 既可以被当做Runnable 来执行,也可以被当做Future 来获取Callable 的返回结果。
    3. 当把Runnable接口或Callable 接口的实现类提交给ExecutorService 执行,调用 submit() 方法时,会返回一个 FutureTask 对象,获取对应返回值。
    4. Callable+Future 可实现多个task 并行执行,但如果前面的task 较慢时需要阻塞
ThreadPoolExecutor 类分析
// 构造函数,创建一个新的ThreadPoolExecutor
public ThreadPoolExecutor(
int corePoolSize, //线程池的核心线程数
int maximumPoolSize, //线程池最大线程数
long keepAliveTime, //当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //任务队列,用来存储等待执行任务的队列
ThreadFactory threadFactory, //线程工厂,用来创建线程,一般默认
RejectedExecutionHandler handler //拒绝策略,当提交的任务过多而不能及时处理时,可定制策略来处理任务
) {
    ...
}
  1. corePoolSize:核心线程数,定义了最少可以同时运行的线程数量;
  2. maximumPoolSize:当队列中存放的任务达到队列容量时,当前可同时运行的线程最大线程数;
  3. workQueue:当新任务来时,先判断当前运行的线程数是否达到核心线程数。如果达到了,线程就会被存放在队列中;
    1. ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
    2. LinkedBlockingQueue:基于链表的先进先出队列,默认是Integer.MAX_VALUE;
    3. SynchronousQueue:不会保存提交的任务,而是将直接新建一个线程来执行新的任务;
    4. PriorityBlockingQueue:类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。
  4. keepAliveTime:当线程池中的线程数大于 corePoolSize 时,如果这时没有新任务提交,核心线程外的多余空闲线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime 才会被回收销毁。
  5. unit:keepAliveTime 参数的时间单位。
  6. threadFactory:Executor 创建新线程时会用到。
  7. handler:饱和策略(或拒绝策略)。当同时运行的线程数达到最大线程数,且队列也已经被填满了时触发。
    1. ThreadPoolExecutor.AbortPolicy:默认策略,抛出 RejectedExecutionException 来拒绝新任务的处理;
    2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛异常;
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务;
    4. ThreadPoolExecutor.CallerRunsPolicy:由当前调用线程(提交任务的线程)来处理该任务。

             

        注:Tomcat 里面的线程池的运行过程是:如果核心线程数用完了,接着用最大线程数,最后才提交任务到队列里面去的。这样是为了保证响应时间优先。

        

常用的四种线程池

建议采用ThreadPoolExecutor构造函数来创建线程池,自定义缓冲队列,控制线程数数量。

FixedThreadPool
  1. 称为可重用固定线程数的线程池。可控制线程最大并发数,每提交一个任务就创建一个工作线程,超出的线程会在队列中等待。
ExecutorService fixed = Executors.newFixedThreadPool(3);

/**
 * 创建一个固定线程的线程池。
 * corePoolSize和maximumPoolSize 都被设置为nThreads
 *
**/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThread, 
                                  nThread,
0L,
                                  TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
  1. 弊端:由于使用的是无界队列 LinkedBlockingQueue 作为缓冲队列,其队列的容量是 Integer.MAX_VALUE,从而导致以下问题:
    1. 由于队列不会满,所以不存在满足丢弃策略条件,从而导致任务比较多时,出现内存溢出情况。
    2. corePoolSize = maximumPoolSize
    3. 但线程池中的线程数达到 corePoolSize 后,新任务会在无界队列中等待,因此线程池中的线程数不会超过corePoolSize;
SingleThreadExecutor
  1. 只有一个线程的线程池。即只创建唯一的工作线程来执行任务。
ExecutorService single = Executors.newSingleThreadExecutor();

/**
 * 创建一个只有一个线程的线程池。
 * corePoolSize和maximumPoolSize 都被设置为 1
 *
**/
public static ExecutorService newSingleThreadExecutor() {
        returnnew FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 
1,
0L, 
                                    TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
    }
CachedThreadPool
  1. 是一个根据需要创建新线程的线程池。
ExecutorService cached = Executors.newCachedThreadPool();

/**
 * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它
 * corePoolSize = 0;maximumPoolSize = Integer.MAX_VALUE
 *
**/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 
                                  Integer.MAX_VALUE,
60L,
                                  TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
  1. 弊端:maximumPoolSize = Integer.MAX_VALUE,即是无界的,意味着如果主线程提交任务的速度高于 maximunPoolSize 中线程处理任务的速度时,CachedThreadPool 会不断创建新的线程。极端情况下,会导致耗尽CPU和内存资源。
    1. 可用于执行一些生存期很短的异步型任务,不适用于IO等长延时操作。

ScheduledThreadPool

  1. 创建一个定长的线程池,而且支持定时的以及周期性的任务执行。
ScheduledExecutorService schedul = Executors.newScheduledThreadPool(10);

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
  1. 使用场景
    1. 登陆后用户信息处理(用户登陆后应该通知各相关系统将用户经常使用数据进行缓存 以快速响应登陆用户);
    2. 生成各大区报表数据;
    3. 异步通讯时;对于执行结果不关注时,可使用多线程;比如发微博,记录日志;
    4. 生成报表等定时任务;涉及到大数据量的计算,可多线程去处理;
    5. 文件读取和处理时;一个线程读取文件,一个线程处理文件;充分利用CPU等待磁盘IO的空闲时间。
    6. 线程池大小设置
      1. CPU 密集型任务,消耗的主要是CPU 资源,可将线程数设置为N(CPU核心数) + 1。
      2. I/O 密集型任务,系统会用大部分时间来处理 I/O交互,而线程在处理 I/O 的时间段内不会占用CPU来处理,这是可将CPU交给其它线程使用。可将线程数设置为 2N。
      3. 通过压测得到合理的参数配置
      4. 线程池动态调整。对于核心服务中的线程池,我们应该是通过线程池监控,做到提前预警。同时可以通过手段对线程池响应参数,比如核心线程数、队列长度进行动态修改。
ThreadLocal
  1. ThreadLocal 对象可以提供线程局部变量,每个线程Thread 都拥有一份自己的本地副本变量,线程间互不干扰。可通过get() 和 set() 方法来获取默认值或将其值更改未当前线程所存的副本的值,从而避免了线程安全问题。
  2. Thread 类中有一个类型为 ThreadLocal.ThreadLocalMap 的实例变量 threadLocals ,也就是说每个线程有一个自己的ThreadLocalMap。是ThreadLocal的内部类。
  3. ThreadLocalMap 可理解为 ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是null,只有当前线程调用 ThreadLocal 类的 set和get 方法时才创建它们。实际调用的是ThreadLocalMap 类对应的get(),set() 方法
  4. 每个Thread 中都存在一个ThreadLocalMap 变量,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。
public class Thread implements Runnable { 
    ......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
 ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 
    ......
}
  1. 比如在同一个线程中声明了两个 ThreadLocal 对象的话,会使用 Thread 内部的ThreadLocalMap 类型的变量存放数据的。
  2. ThreadLocalMap 的key 存放 ThreadLocal 对象(实际上为对象的弱引用),value 存放ThreadLocal 对象调用set 方法设置的值。
  3. ThreadLocal.set() 方法执行

        

  1. ThreadLocal 内存泄漏
    1. ThreadLocalMap的生命周期和Thread 一样长;使用的是 ThreadLocal 的弱引用作为key;
    2. 如果一个ThreadLocal 没有外部强引用来引用它,那么系统GC 时,该ThreadLocal 变量将会被回收
    3. 回收后,ThreadLocalMap 中就会出现key 为null 的Entry,就没办法访问这些key为null的Entry 的value;
    4. 如果当前线程再迟迟不结束的话(线程池重用线程),这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value 永远无法回收

解决方法:使用完ThreadLocal 后,执行remove 操作,避免出现内存溢出情况。

  1. ThreadLocal 特性
    1. ThreadLocal 和 Synchronized 都是为了解决多线程中相同变量的访问冲突问题。
    2. Synchronized 是通过对象的锁机制,保证同一个事件只有一个线程访问变量。牺牲时间来解决访问冲突;线程间的数据共享。
    3. ThreadLocal 是通过为每个线程单独一份存储空间,牺牲空间来解决冲突。只有在线程内才能获取对应的值。
    4. Spring中,绝大部分Bean都可以声明为singleton作用域,是因为Spring 对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用了ThreadLocal 进行处理,让它们也称为线程安全的状态。
TransmittableThreadLocal
  1. Alibaba 开源组件:transmittable-thread-local;
  2. TransmittableThreadLocal 是一个 Java 线程本地变量,它继承自 InheritableThreadLocal 类并增加了一些功能。与 InheritableThreadLocal 不同的是,TransmittableThreadLocal 可以在线程间传递值而不受父线程修改的影响
  3. TransmittableThreadLocal 主要用于解决使用线程池等情况下,在线程之间传递上下文信息的问题。例如,在 Web 应用程序中,每个请求通常都有一个唯一的请求 ID,如果使用线程池处理请求,需要将请求 ID 从主线程传递给工作线程,否则在工作线程中无法正确地记录日志或者追踪请求。
  4. 通过 TransmittableThreadLocal,可以在主线程中设置请求 ID,然后在线程池中执行任务时,工作线程将自动获取到该请求 ID 的副本,并且不会被主线程修改所影响。这样就可以实现在线程池中正确地处理上下文信息的目的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值