(初识并发)线程的概念?如何快速认识并发

212 阅读5分钟

写在前面

重新去学习并发编程,这边文章带大家重新去整理线程的一些基本知识以及 JAVA 中线程的创建。当然了也适合要学习怎么去使用多线程编程的同学们。

怎么去学习(小tips)?

在准备去学习和理解相关概念的时候,这里有个小技巧分享给大家。就是在接触概念时,我们首先脑海中需要有一副多线程的执行图:

多线程原理图
所谓的多线程,其实就是在程序执行的过程中,分裂出多个线程去执行。但是正因为分裂后的多线程去同时执行任务,可能会导致一些资源抢占、先后顺序等等很多问题。所以设计出了各种各种的东西来控制它。以及检测它。

1. 并发的一些专业术语

1.1 线程安全

一个类多个线程以任何一种方式同时调用,且不需要外部额外同步和协同的情况下,仍然保持内部数据正确表现正确的行为,那么类就是线程安全的。

1.2 线程安全等级

在这里插入图片描述

1.2.1 不可变

不可变的对象一定是线程安全的。如:final修饰的不可变类(String,Integer等);enum枚举类 注:enum编译后其实就是生成一堆 final 类,所以不可变

final 解释
  1. final是当你创建一个对象时,使用final关键字能够使得另外一个线程不会访问到处于“部分创建”的对象,否则不能保证线程安全
  2. final 只是用来保证值不能被直接覆盖的。

1.2.2 线程安全类

类中的任意方法都不会使得数据处于不一致或者数据污染的情况。java.util.concurrent包下的类。

1.2.3 有条件的线程安全

对于单独访问类的安全,是线程安全。但是对于某些复合操作,需要外部类来同步。

1.2.4 线程对立类

线程对立类是那些不管是否调用了外部同步都不能 在并发使用时保证其安全的类。

1.3 同步异步、阻塞非阻塞

1.3.1 同步的概念

阻塞式调用,调用必须等待响应对方执行完毕才会返回。

1.3.2 异步的概念

非阻塞式调用,立即返回,调用方无需等待响应返回实际结果,响应方会通过状态、通知或回调来告知调用方。

3.2.1 应用场景

  1. 耗时任务。主线程提交耗时任务到线程池,通过 Feture 来异步获取任务执行结果。
  2. 电商下单链路的非核心链路调用:为下单的性能考虑,将订单下发的发货仓库等非实时的流程放在后续操作,提高下单的响应速度。

1.4. 并发并行

1.4.1 并发

一个线程处理多个事件,在时间线上,通过切换来达到效果。

1.4.2 并行

多个处理器处理多个事件。

2. JAVA 中的线程,以及怎么去创建线程的?

在java中,每一条线程就是 Thread 类。

2.1. 线程的状态

NEW: 被创建但是未运行,未调用 start() 方法 RUNNABLE: 可被执行(正在执行RUNNING、在JVM里面在等待执行READY) BLOCKED: 阻塞,正在等待监视器锁,来进入或重入synchronized代码块或方法 WAITING: 被执行 wait() 的等待 TIMEWAITING: 时间等待 ERMINATED: 终结状态,已经执行完成

在这里插入图片描述

2.2 Thread 类中部分方法讲解:

Threard.yield() 线程让步,当前线程退出时间片,让其他线程活当前线程使用CPU时间片执行 Thread.sleep() 线程休眠,主动让出当前CPU时间,在指定时间过后,CPU会返回继续执行该线程。但是! sleep 方法不会释放当前锁持有的锁。 Thread.join() 等待该线程死亡/终止,当前线程会等待调用该方法的线程执行完后才继续执行。 Object.wait() Object 类的方法,调用前必须拥有对象锁,例如在synchroized代码块内,调用wait方法后,对象锁会释放,线程进入 WAITING 等待状态。

为什么会有这些方法? 其实就是为了在 主线程 执行过程中,对多线程之间执行的顺序进行控制。如果单单从多个new出来的线程进行分析,是很容易懵的。在理解概念的时候,我们应该把 程序主线程 给考虑进去,这样会容易理解一点。

3. 并发的问题:死锁

两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信二造成的阻塞现象。没有外力作用,它们将无法推进下去。(简单的描述为:两个线程/进程占有着资源,却还想去拿对方正在占用的资源。打个比方两个小孩自己手上拿着玩具不放手,又嚷嚷着要拿对方手里的玩具。此时就会出现死锁。特点就是如果没有人出来帮忙,就会一直死锁下去,互不让步)

在这里插入图片描述

3.1 死锁原因

两个及以上的线程,抢占2把及以上的锁,抢占锁的顺序不一致。

3.2 避免和处理死锁思路

1)不使用锁,不使用2把以及2把以上的锁 2)必须使用2把或2把以上的锁时,必须保证获取锁的顺序是一致的 3)尝试获得具有超时释放的锁,例如 Lock 中的tryLock来获取锁 4)当发生了 JAVA-level 的锁时,重启程序来干掉进程/线程

3.3 如何定位死锁的位置?

1) JPS 拿到 pid 2) jstack 拿到JVM线程的状态,通过状态 或 看看有没有相关关键字(java-deadlock)关键字