java并发编程之美【一】线程基础

1什么是线程

进程是代码在数据集合上的一次运行活动 , 是系统进行资源分配和调度的基本单位 , 线程则是进程的一个执行路径, 一个进程中至少有一个线程,进程中的多个线程共享进程的 资源
操作系统在分配资源时是把资源分配给进程的, 但是 CPU 资源 比较特殊 ,它是被分配到线程的 , 因为真正要占用 CPU 运行的是线程 , 所以也说线程是 CPU 分配的基本单位
在 Java 中,当我们启动 main 函数时其实就启动了一个JVM 的进程, 而 main 函数所在的线程就是这个进程中的一个线程,也称主线程 。
在这里插入图片描述
程序计数器是一块内存区域,用来记录线程当前要执行的指令地址 。

线程是占用 CPU 执行的基本单位,而 CPU 一般是使用时间片轮转方式让线程轮询占用的,所以当前线程 CPU 时间片用完后,要让出CPU ,等下次轮到 自 己的时候再执行 。
程序计数器就是为了记录该线程让出 CPU 时的执行地址的,待再次分配到时间片时线程就可以从自己私有的计数器指定地址继续执行 。

每个线程都有自 己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,其他线程是访问不了的,除此之外枝还用来存放线程的调用技帧 。
是一个进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的,堆里面主要存放使用 new 操作创建的对象实例 。
方法区则用来存放 NM 加载的类、常量及静态变量等信息,也是线程共享的 。

2 线程创建与运行

Java 中有 三种 线程创建方式,分别为实现 Runnable 接口的 run 方法,继承 Thread 类并重写 run 的方法,使用 FutureTask 方式(实现了 Callable 接口重写call方法) 。

继承 Thread 类

  • 调用 start 方法后线程并没有马上执行而是处于就绪状态, 这个就绪状态是指该线程已经获取了除 CPU 资源外的其他资源,等待获取 CPU 资源后才会真正处于运行状态。一旦 run 方法执行完毕, 该线程就处于终止状态 。
  • 使用继承方式的好处是 , 在 run() 方法 内 获取当前线程直接使用 this 就可 以了,无须使用 Thread.currentThread()方法 ; 不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么就不能再继承其他类。另外任务与代码没有分离 , 当多个线程执行一样的任务时需要多份任务代码,而 Runable 则没有这个限制 。
    FutureTask 方式
  • CallerTask 类实现了 Callable 接口的 call()方法。在 main 函数内首先创建 了 一 个 FutrueTask 对象( 构造函数为 CallerTask 的 实例) , 然后使用创建的 FutrueTask对象作为任务创建了一个线程并且启动它, 最后通过 futueTask.get() 等待任务执行完毕并返回结果 。

使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过 set方法设置参数或者通过构造函数进行传递,而如果使用 Runnable 方式,则只能使用主线程里面被声明为 final 的变量。不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么子类不能再继承其他类,而 Runable 则没有这个限制 。前两种方式都没办法拿到任务的返回结果,但是 Futuretask 方式可以 。

3线程通知与等待

Java 中的 Object 类是所有类的父类,鉴于继承机制, Java 把所有类都需要的方法放到 了 Object 类里面,其中就包含通知与等待系列函数。

1. wait()函数

当一个线程调用 一个共享变量的 wait()方法时, 该调用线程会被阻塞挂起, 直到发生下面几件事情之一才返回 :
1 其他线程调用了该共享对象的 notify()或者 notifyAll()方法 ;
2 其他线程调用了该线程 的 interrupt()方法 , 该线程抛出 InterruptedException 异常返回。

当前线程调用共享变量的 wait()方法后只会释放当前共享变量上的锁,如果 当前线程还持有其他共享变量的锁,则这些锁是不会被释放的 。
当一个线程调用 共享对象 的 wait()方法被阻塞挂起后,如果其他线程中断了该线程, 则 该线程会抛出 InterruptedException 异常并返回 。

2. wait(long timeout)函数

3. wait(long timeout, int nanos)函数

在其内部调用的是 wait(long timeout)函数

4. notify()函数

一个线程调用共享对象的 notify()方法后,会唤醒一个在该共享变量上调用 wait 系列方法后被挂起的线程。 一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。

被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁, 只有该线程竞争到了共享变量的监视器锁后才可 以继续执行。

5. notifyAll ()函数

不同于在共享变量上调用 notify()函数会唤醒被阻塞到该共享变量上 的 一 个线程,notifyAll() 方法则会唤醒所有在该共享变量上由于调用 wait 系列方法而被挂起的线程

4 等待线程执行终止的 join 方法

在项目实践中经常会遇到一个场景,就是需要等待某几件事情完成后才能继续往下执行 , 比如多个线程加载资源 , 需要等待多个线程全部加载完毕再汇总处理。 Thread 类中有一个 join 方法就可以做这个事情,前面介绍的等待通知方法是 Object 类中的方法 , 而 join方法则 是 Thread 类直接提供的 。

5 让线程睡眠的 sleep 方法

Thread 类 中有一个静态的 sleep 方法,当 一个执行中的线程调用了 Thread 的 sleep 方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的

6 让出 CPU 执行权的 yield 方法

Thread 类 中 有一个静态 的 yield 方法,当一个线程调用 yield 方法时,实际就是在暗示线程调度器当前线程请求让出自己 的 CPU 使用,但是线程调度器可以无条件忽略这个暗示。

sleep 与 yield 方法的区别在于,当线程调用 sleep 方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程 。 而调用 yield 方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行 。

7 线程中断??

Java 中的线程中断是 一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行 , 而是被中断的线程根据中断状态自行处理 。

8 理解线程上下文切换

在多线程编程中,线程个数一般都大于 CPU 个数,而每个 CPU 同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转的策略 ,也就是给每个线程分配一个时间片,线程在时间片内占用 CPU 执行任务 。当前线程使用完时间片后,就会处于就绪状态并让出 CPU 让其他线程占用 , 这就是上下文切换 ,从当前线程的上下文切换到了其他线程 。
在切换线程上下文时需要保存当前线程的执行现场 , 当再次执行时根据保存的执行现场信息恢复执行现场 。
线程上下文切换时机有 : 当前线程的 CPU 时间片使用完处于就绪状态时,当前线程被其他线程中断时 。

9线程死锁

1 什么是线程死锁
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去
在这里插入图片描述
死锁的产生必须具备以下四个条件 :互斥条件、请求并持有条件、不可剥夺条件、环路等待条件

2 如何避免线程死锁
要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可 , 但是学过操作系统的读者应该都知道,目前只有请求并持有和环路等待条件是可以被破坏的 。

10 守护线程与用户线程

Java 中的 线程分为两类,分别为 daemon 线程(守护线程〉和 user 线程(用户线程)。
在JVM 启动时会调用 main 函数, main 函 数所在的钱程就是一个用户线程,其实在JVM内部同时-还启动了 好多守护线程, 比如垃圾回收线程。

当最后一个非守护线程结束时, NM 会正常退出,而不管当前是否有守护线程 ,也就是说守护线程是否结束并不影响 NM 的退出。言外之意,只要有一个用户线程还没结束 , 正常情况下 NM 就不会退出。

11 ThreadLocal

ThreadLocal 是 JDK 包提供的,它提供了线程本地变量 ,也就是如果你创 建了 一 个ThreadLocal 变量 ,那么访问这个变量 的每个线程都会有这个变量的一个本地副本(复制一个变量到自己的本地内存) 。 当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals , 它们 都是 ThreadLocalMap 类型 的变量 , 而 ThreadLocalMap 是一个定制化的 Hashmap 。 在默认情况下, 每个线程中的这两个变量都为 null,只有当前线程第一次调用 ThreadLocal 的 set 或者 get 方法时才会创建它们 。 其实每个线程的本地变量不是存放在 ThreadLocal 实例 里面,而是存放在调用线程的 threadLocals 变量里面 。 也就是说 , ThreadLocal 类型的本地变量存放在具体的线程内存空间中 。 ThreadLocal 就是一个工具壳,它通过 set 方法把 value 值放入调用线程的 threadLocals 里面并存放起来 , 当调用 线程调用它的 get 方法时,再从当 前线程的 threadLocals 变量里面将其拿出来使用 。 如果调用线程一直不终止, 那么这个本地变量会一直存放在调用线程的 threadLocals 变量里面 ,所以当不需要使用本地变量时可以通过调用 ThreadLocal 变量的 remove 方法 ,从当前线程的 threadLocals 里面删除该本地变量 。另外, Thread 里面 的 threadLocals 为何被设计为 map 结构?很明显是因为每个线程可以关联多个 ThreadLocal 变量

Threadlocal 不支持继承性;也就是说,同一个 ThreadLocal 变量在父线程中被设置值后 , 在子线程中 是获取不到的。
InheritableThreadLocal继承自 ThreadLocal , 其提供了一个特性,就是让子线程可 以访问在父线程中设置的本地
变量 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值