Java并发基础知识,我用思维导图整理好了

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 14.5、自旋锁

话不多说,先上图。

并发基础

1、基本概念

========================================================================

欲说线程,必先说进程。

  • 进程:进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。

  • 线程:线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

操作系统在分配资源时是把资源分配给进程的, 但是 CPU 资源比较特殊,它是被分配到线程的,因为真正要占用CPU运行的是线程,所以也说线程是 CPU分配的基本单位

在Java中,当我们启动 main 函数其实就启动了一个JVM进程,而 main 函数在的线程就是这个进程中的一个线程,也称主线程。

示意图如下:

程序进程线程关系

一个进程中有多个线程,多个线程共用进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈。

2、线程创建和运行

===========================================================================

Java中创建线程有三种方式,分别为继承Thread类、实现Runnable接口、实现Callable接口。

  • 继承Thread类,重写run()方法,调用start()方法启动线程

public class ThreadTest {

/**

  • 继承Thread类

*/

public static class MyThread extends Thread {

@Override

public void run() {

System.out.println(“This is child thread”);

}

}

public static void main(String[] args) {

MyThread thread = new MyThread();

thread.start();

}

}

  • 实现 Runnable 接口run()方法

public class RunnableTask implements Runnable {

public void run() {

System.out.println(“Runnable!”);

}

public static void main(String[] args) {

RunnableTask task = new RunnableTask();

new Thread(task).start();

}

}

上面两种都没有返回值。

  • 实现Callable接口call()方法,这种方式可以通过FutureTask获取任务执行的返回值

public class CallerTask implements Callable {

public String call() throws Exception {

return “Hello,i am running!”;

}

public static void main(String[] args) {

//创建异步任务

FutureTask task=new FutureTask(new CallerTask());

//启动线程

new Thread(task).start();

try {

//等待执行完成,并获取返回结果

String result=task.get();

System.out.println(result);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

}

3、常用方法

========================================================================

3.1、线程等待与通知


在Object类中有一些函数可以用于线程的等待与通知。

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

  • wait(long timeout) :该方法相 wait() 方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的 timeout ms时间内被其它线程调用该共享变量的notify()或者 notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。

  • wait(long timeout, int nanos),其内部调用的是 wait(long timout)函数。

上面是线程等待的方法,而唤醒线程主要是下面两个方法:

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

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

如果有这样的场景,需要等待某几件事情完成后才能继续往下执行,比如多个线程加载资源,需要等待多个线程全部加载完毕再汇总处理。Thread类中有一个join方法可实现。

3.2、线程休眠


Thread类中有一个静态态的 sleep 方法,当一个个执行中的线程调用了Thread 的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与 CPU 的调度,获取到 CPU 资源后就可以继续运行。

3.3、让出优先权


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

当一个线程调用 yield 方法时, 当前线程会让出 CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出 CPU 的那个线程来获取 CPU 行权。

3.4、线程中断


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

  • void interrupt() :中断线程,例如,当线程A运行时,线程B可以调用钱程interrupt() 方法来设置线程的中断标志为 true 并立即返回。设置标志仅仅是设置标志, 线程A实际并没有被中断, 会继续往下执行。如果线程A因为调用了wait() 系列函数、 join 方法或者 sleep 方法阻塞挂起,这时候若线程 B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException异常而返回。

  • boolean isInterrupted() 方法: 检测当前线程是否被中断。

  • boolean interrupted() 方法: 检测当前线程是否被中断,与 isInterrupted 不同的是,该方法如果发现当前线程被中断,则会清除中断标志。

4、线程状态

========================================================================

上面整理了线程的创建方式和一些常用方法,可以用线程的生命周期把这些方法串联起来。

在Java中,线程共有六种状态:

| 状态 | 说明 |

| — | — |

| NEW | 初始状态:线程被创建,但还没有调用start()方法 |

| RUNNABLE | 运行状态:Java线程将操作系统中的就绪和运行两种状态笼统的称作“运行” |

| BLOCKED | 阻塞状态:表示线程阻塞于锁 |

| WAITING | 等待状态:表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |

| TIME_WAITING | 超时等待状态:该状态不同于 WAITIND,它是可以在指定的时间自行返回的 |

| TERMINATED | 终止状态:表示当前线程已经执行完毕 |

线程在自身的生命周期中, 并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换,Java线程状态变化如图示:

Java线程状态变化

5、线程上下文切换

===========================================================================

使用多线程的目的是为了充分利用CPU,但要认识到,每个CPU同一时刻只能被一个线程使用。

线程切换-2020-12-16-2107

为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转也就是给每个线程分配一个时间片,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会处于就绪状态并让出 CPU 让其他线程占用,这就是上下文切换。

image-20210202172806362

6、线程死锁

========================================================================

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。

image-20210202173326028

那么为什么会产生死锁呢? 死锁的产生必须具备以下四个条件:

  • 互斥条件:指线程对己经获取到的资源进行它性使用,即该资源同时只由一个线程占用。如果此时还有其它线程请求获取获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。

  • 请求并持有条件:指一个 线程己经持有了至少一个资源,但又提出了新的资源请求,而新资源己被其它线程占有,所以当前线程会被阻塞,但阻塞 的同时并不释放自己已经获取的资源。

  • 不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其它线程抢占,只有在自己使用完毕后才由自己释放该资源。

  • 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即线程集合 {T0,T1,T2,…… ,Tn} 中 T0 正在等待一 T1 占用的资源,Tl1正在等待 T2用的资源,…… Tn 在等待己被 T0占用的资源。

该如何避免死锁呢?答案是至少破坏死锁发生的一个条件

其中,互斥这个条件我们没有办法破坏,因为用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的,到底如何做呢?

  • 对于“请求并持有”这个条件,可以一次性请求所有的资源。

  • 对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。

  • 对于“环路等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后就不存在环路了。

7、线程分类

========================================================================

Java中的线程分为两类,分别为 **daemon 线程(守护线程)**和 user 线程(用户线程)

在JVM 启动时会调用 main 函数,main函数所在的钱程就是一个用户线程。其实在 JVM 内部同时还启动了很多守护线程, 比如垃圾回收线程。

那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程束时, JVM会正常退出,而不管当前是否存在守护线程,也就是说守护线程是否结束并不影响 JVM退出。换而言之,只要有一个用户线程还没结束,正常情况下JVM就不会退出。

8、ThreadLocal

===============================================================================

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

image-20210202182241538

可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。

下面来看一个ThreadLocal的使用实例:

public class ThreadLocalTest {

//创建ThreadLocal变量

static ThreadLocal localVar = new ThreadLocal();

//打印函数

static void print(String str) {

//打印当前线程本地内存中localVar变量值

System.out.println(str + “:” + localVar.get());

//清除前线程本地内存中localVar变量值

//localVar.remove();

}

public static void main(String[] args) {

Thread thread1 = new Thread(new Runnable() {

public void run() {

//设置线程1中本地变量localVal的值

localVar.set(“线程1的值”);

//调用打印函数

print(“线程1”);

//打印本地变量的值

System.out.println(“线程1打印本地变量后:” + localVar.get());

}

});

Thread thread2 = new Thread(new Runnable() {

public void run() {

//设置线程2中本地变量localVal的值

localVar.set(“线程2的值”);

//调用打印函数

print(“线程2”);

//打印本地变量的值

System.out.println(“线程2打印本地变量后:” + localVar.get());

}

});

thread1.start();

thread2.start();

}

}

9、Java内存模型

============================================================================

在Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享 。

Java线程之间的通信由Java内存模型控制,Java内存模型决定一个线程对共享变量的写入何时对另一个线程可见。

从抽象的角度来看,Java内存模型定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是Java内存模型的 一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

Java内存模型的抽象示意如图:

《MySql面试专题》

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

《MySql性能优化的21个最佳实践》

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

《MySQL高级知识笔记》

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

文中展示的资料包括:**《MySql思维导图》《MySql核心笔记》《MySql调优笔记》《MySql面试专题》《MySql性能优化的21个最佳实践》《MySq高级知识笔记》**如下图

全网火爆MySql 开源笔记,图文并茂易上手,阿里P8都说好

关注我,点赞本文给更多有需要的人
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
.(img-rNHAFvFS-1714686493173)]

[外链图片转存中…(img-4BT68Dud-1714686493173)]

[外链图片转存中…(img-E0ibVT4m-1714686493173)]

[外链图片转存中…(img-fVGbArv3-1714686493174)]

[外链图片转存中…(img-r0GZhmfM-1714686493174)]

[外链图片转存中…(img-ib3mJOP2-1714686493174)]

[外链图片转存中…(img-VbGPFRv0-1714686493174)]

[外链图片转存中…(img-bQ0hVx7t-1714686493175)]

文中展示的资料包括:**《MySql思维导图》《MySql核心笔记》《MySql调优笔记》《MySql面试专题》《MySql性能优化的21个最佳实践》《MySq高级知识笔记》**如下图

[外链图片转存中…(img-jqVv6nnt-1714686493175)]

关注我,点赞本文给更多有需要的人
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值