线程的3种实现方式并深入源码简单分析实现原理

前言

本文介绍下线程的3种实现方式并深入源码简单的阐述下原理

三种实现方式

Thread

Runnable

Callable&Future

深入源码简单刨析

Thread

 Thread类实现了Runnable接口

枚举类 State

JVM中的线程必须只能是以上6种状态的一种。这些状态是JVM状态并不能和操作系统线程状态互相映射
  • NEW

线程刚创建,还未执行(start方法)
  • RUNNABLE

已就绪可运行的状态。
处于此状态的线程是正在JVM中运行的,
但可能在等待操作系统级别的资源,例如CPU时间片
  • BLOCKED

阻塞等待监视器锁
处于此状态的线程正在阻塞等待监视器锁,
以进入一个同步块/方法,
或者在执行完wait()方法后重入同步块/方法
  • WAITING

等待
执行完Object.wait无超时参数操作,
或者 Thread.join无超时参数操作(进入等待指定的线程执行结束),
或者 LockSupport.park操作后,线程进入等待状态。
一般在等待状态的线程在等待其它线程执行特殊操作,
例如:等待另其它线程操作Object.notify()唤醒或者Object.notifyAll()唤醒所有。
  • TIMED_WAITING

限时等待
Thread.sleep、Object.wait带超时时间、
Thread.join带超时时间、
LockSupport.parkNanos、
LockSupport.parkUntil这些操作会时线程进入限时等待
  • TERMINATED

终止,线程执行完毕

线程状态流转

JVM 线程状态流转图

注意:不要混淆操作系统线程状态和java线程状态。JVM中的线程必须只能是以上6种状态的一种!RUNNABLE = 正在JVM中运行的(Running)+ 可能在等待操作系统级别的资源(Ready)例如CPU时间片

  • 线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源),

  • 只有线程运行需要的所有条件满足了,才进入就绪状态。

  • 当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。

  • 当得到CPU执行时间之后,线程便真正进入运行状态。

  • 线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

  • 当由于突然中断或者子任务执行完毕,线程就会被消亡。

关键属性

  • name

表示Thread的名字,
可以通过Thread类的构造器中的参数来指定线程名字
  • priority

线程的优先级(最大值为10,最小值为1,默认值为5)
  • daemon

线程是否是守护线程,如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。在JVM中,垃圾收集器线程就是守护线程
  • target

要执行的任务
  • group

线程群组

关键方法

  • start

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源
  • run

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务
  • sleep

有2个重载版本

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos) throws InterruptedException;
sleep让线程睡眠,交出CPU,让CPU去执行其他的任务。
sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,
则即使调用sleep方法,其他线程也无法访问这个对象。
sleep方法相当于让线程进入阻塞状态。
  • yield

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。
它跟sleep方法类似,同样不会释放锁。
但是yield不能控制具体的交出CPU的时间,
另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,
而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,
这一点是和sleep方法不一样的。
  • join

有三个重载版本

join()
join(long millis)     //参数为毫秒
join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

可以看出,当调用thread.join()方法后,main线程会进入等待,然后等待thread执行完之后再继续执行

join方法实际上调用的是Object的wait方法
wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
  • interrupt

nterrupt,中断。
单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,
也就说,它可以用来中断一个正处于阻塞状态的线程
  • stop

stop方法已经是一个废弃的方法,它是一个不安全的方法。
因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,
如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。
所以stop方法基本是不会被用到的。

Runnable

函数式编程 FunctionInterface

该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。
加上该注解能够更好地让编译器进行检查。
如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
// 正确的函数式接口
@FunctionalInterface
public interface TestInterface {
 
    // 抽象方法
    public void sub();
 
    // java.lang.Object中的public方法
    public boolean equals(Object var1);
 
    // 默认方法
    public default void defaultMethod(){
    
    }
 
    // 静态方法
    public static void staticMethod(){
 
    }
}

// 错误的函数式接口(有多个抽象方法)
@FunctionalInterface
public interface TestInterface2 {

    void add();
    
    void sub();
}

方法修饰符 default

目的是让接口可以拥有具体的方法,让接口内部包含了一些默认的方法实现

如果一个类、类属变量及方法没有用任何修饰符
(即没有用public、protected及private中任何一种修饰)
则其访问权限为default(默认访问权限)

Callable&Future

Callable

Future

  • cancel(boolean mayInterruptIfRunning)

取消任务
参数:是否立即中断任务执行,或者等等任务结束
  • isCancelled()

任务是否已经取消,若已取消,返回true

若计算还没有开始,它被取消且不再开始。
若计算处于运行之中,那么如果mayInterrupt参数为true,它就被中断。
  • isDone()

任务是否已经完成。包括任务正常完成、抛出异常或被取消,都返回true
  • V get() throws InterruptedException, ExecutionException

等待任务执行结束,获得V类型的结果。
InterruptedException: 线程被中断异常,
ExecutionException: 任务执行异常,
如果任务被取消,还会抛出CancellationException
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。
如果计算超时,将抛出TimeoutException

设置了超时时间可以防止程序无限制的等待future的返回结果

线程池

核心类

Executor

Executor类族是基于生产者-消费者思想设计的,
提交任务相当于生产者生产任务,
而执行任务相当于消费者消费任务

ExecutorService

继承Executor 添加了一些用于生命周期管理的方法
  • shutdown()

将执行平缓的关闭过程:不再接受新的任务,并且等待已经提交的任务完成
  • shutdownNow()

表示立即关闭
  • isShutdown和isTerminated

判断是否关闭或者终止
  • 三个submit

用于提交的任务,可以用Runnable作为任务,也可以用Callable作为任务
  • 工厂类Executors

用于创建线程池

1、newFixedThreadPool

固定长度线程池
每当提交任务创建一个线程,直到线程池的最大数量,
该方法返回的是ExecutorService对象,可以看出该方法调用了ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()),其中

第一个参数表示核心线程数,即初始化创建的线程数;
第二个参数是如果最大线程数,即当任务比较多的时候,会自动增加线程数,但不得超过该值;
第三个参数表示等待时间,当超过该时间线程没收到任务则会收回线程;
第四个参数是第三个参数的单位;
最后一个参数是一个阻塞队列,用于存放工作任务

2、newSingleThreadExecutor

单线程,这里调用了Executors工厂类的静态方法newSingleThreadExecutor,
创建ThreadPoolExecutor对象时,将线程数限制在1即可。
其它如固定长度线程池,只不过是最多只有一个线程工作

3、newCachedThreadPool

创建一个可缓存的线程池,
如果线程池规模过大,回收空闲线程,
如果需求增加,则添加新的线程。
返回的是
new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
从返回的对象可以看出,线程初始值是0,最大值是Integer.MAX_VALUE,所以能够使得线程数是动态变化的。

4、newScheduledThreadPool

创建固定长度的线程池,以延迟或定时的方式来执行任务,返回ScheduledThreadPoolExecutor对象

通过以上四种静态方法构造的线程池中,主要涉及到两个线程池类,一个是ScheduledThreadPoolExecutor,一个是ThreadPoolExecutor

a、ScheduledThreadPoolExecutor

b、ThreadPoolExecutor

小结

Executor为了解决多线程任务调度的问题,可以合理平衡系统吞吐量与内存管理。
Executor是一个最顶层的接口,为了更好管理Executor的生命周期,
便扩展了ExceutorService接口,该类有具体实现类ThreadPoolExecutor和ScheduledExecutorService。
并通过工厂类Executors的几个静态方法来创建不同功用的线程池

本文使用 mdnice 排版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值