# 多线程

# 并行和并发有什么区别?

第十六章、垃圾回收相关概念 | 编程手册 (opens new window)

# 线程和进程的区别?

参考:

线程与进程,你真的清楚吗?_CSDN资讯-CSDN博客 (opens new window)

进程是操作系统分配资源的单位,线程是调度的基本单位,线程之间共享进程资源

线程就是函数的调用流程,每个线程由自己的栈帧

image

每个线程都有自己独立的、私有的栈区。

所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器是线程私有的。

image

# 线程间共享的资源

image

# 代码区域

image

# 数据区

C语言中的全局变量

image

# 堆区

new出来的对象

image

# 动态链接库

源码生成可执行文件,

静态链接:一股脑生成一个exe文件

动态链接:动态链接的意思是我们不把动态链接的部分打包到可执行程序,而是在可执行程序运行起来后去内存中找动态链接的那部分代码,

image

image

# 文件

image

# 说一下 runnable 和 callable 有什么区别?

Runnable和Callable的区别是bai, (1)Callable规定的方法是call(),Runnable规定的方法是run(). (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得 (3)call方法可以抛出异常,run方法不可以 (4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

# 线程有哪些状态?

线程的5种状态详解_老猫的博客-CSDN博客_线程的几种状态 (opens new window)

image

# sleep() 和 wait() 有什么区别?

sleep() 和 wait() 有什么区别? - 清晨的第一抹阳光 - 博客园 (opens new window)

sleep()和wait()都是线程暂停执行的方法。

1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。 2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。 3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。 4、sleep()方法必须捕获异常InterruptedException,而wait()\notify()以及notifyAll()不需要捕获异常。

注意:

sleep方法只让出了CPU,而并不会释放同步资源锁。

线程执行sleep()方法后会转入阻塞状态。

sleep()方法指定的时间为线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度。

# notify()和 notifyAll()有什么区别?

notify()和notifyAll()都是baiObject对象用于通du知处在等待该对象的线zhi程dao的方法。zhuan void notify(): 唤醒一个正在shu等待该对象的线程。 void notifyAll(): 唤醒所有正在等待该对象的线程。 两者的最大区别在于: notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。 notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify notifyAll,它们等待的是被notify或notifyAll,而不是锁。

# 线程的 run()和 start()有什么区别?

  • 调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
  • 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。

# 创建线程池有哪几种方式?

创建线程池有哪几种方式_XiaoY的专栏-CSDN博客_线程池的创建方式有几种 (opens new window)

index method corePoolSize maximumPoolSize keepAliveTime unit workQueue
1 newCachedThreadPool 0 Integer.MAX_VALUE 60L TimeUnit.SECONDS SynchronousQueue
2 newFixedThreadPool 自定义 与corePoolSize相同 0L TimeUnit.MILLISECONDS LinkedBlockingQueue
3 newSingleThreadExecutor 1 1 0L TimeUnit.MILLISECONDS LinkedBlockingQueue
4 newScheduledThreadPool 自定义 Integer.MAX_VALUE 0 NANOSECONDS DelayedWorkQueue
5 newSingleThreadScheduledExecutor 1 Integer.MAX_VALUE 0 NANOSECONDS DelayedWorkQueue

# 线程池都有哪些状态?

1.RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。

2.SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。

3.STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4.TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。

当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。

当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5.TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

# 线程池中 submit()和 execute()方法有什么区别?

  • execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
  • execute() 没有返回值;而 submit() 有返回值
  • submit() 的返回值 Future 调用get方法时,可以捕获处理异常

# 在 Java 程序中怎么保证多线程的运行安全?

线程的安全性问题体现在:

  • 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
  • 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
  • 有序性:程序执行的顺序按照代码的先后顺序执行

导致原因:

  • 缓存导致的可见性问题
  • 线程切换带来的原子性问题
  • 编译优化带来的有序性问题

解决办法:

  • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
  • synchronized、volatile、LOCK,可以解决可见性问题
  • Happens-Before 规则可以解决有序性问题

图解 Java 线程安全 (opens new window)

image

image

# 线程冲突的列子

image

# 多线程锁的升级原理是什么?

# 什么是死锁? 怎么防止死锁?

Java基础 什么事思索?怎么防止死锁 | 编程手册 (opens new window)

# ThreadLocal 是什么?有哪些使用场景?

Java 提供的一种保存线程私有信息的机制,因为其在整个线程生命周期内有效,所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务 ID、Cookie 等上下文相关信息。

废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应 ThreadLocalMap!这就是很多 OOM 的来源,所以通常都会建议,应用一定要自己负责 remove,并且不要和线程池配合,因为 worker 线程往往是不会退出的。

# 说一下 synchronized 底层实现原理?

Java基础-synchronized底层实现原理 | 编程手册 (opens new window)

# synchronized 和 volatile 的区别是什么?

synchronized详解 (opens new window)

volatile和synchronized的区别_Heaven Wang 的专栏-CSDN博客_synchronized和volatile区别 (opens new window)

# volatile和synchronized的区别

  1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别
  3. volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

4个点说清楚Java中synchronized和volatile的区别 (opens new window)

1、Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等。

2、synchronized通过加锁的方式,使得其在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的。的确,大部分并发控制操作都能使用synchronized来完成。

3、volatile通过在volatile变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和有序性。

4、volatile关键字是无法保证原子性的,而synchronized通过monitorenter和monitorexit两个指令,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,即可保证不会出现CPU时间片在多个线程间切换,即可保证原子性。

image

# synchronized 和 ReentrantLock 区别是什么?

  • synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
  • synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
  • synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
  • synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
  • synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
  • synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放

# 说一下 atomic 的原理?

JDK Atomic开头的类,是通过 CAS 原理解决并发情况下原子性问题。

CAS 包含 3 个参数,CAS(V, E, N)。V 表示需要更新的变量,E 表示变量当前期望值,N 表示更新为的值。只有当变量 V 的值等于 E 时,变量 V 的值才会被更新为 N。如果变量 V 的值不等于 E ,说明变量 V 的值已经被更新过,当前线程什么也不做,返回更新失败。

当多个线程同时使用 CAS 更新一个变量时,只有一个线程可以更新成功,其他都失败。失败的线程不会被挂起,可以继续重试 CAS,也可以放弃操作。

CAS 操作的原子性是通过 CPU 单条指令完成而保障的。JDK 中是通过 Unsafe 类中的 API 完成的。

在并发量很高的情况,会有大量 CAS 更新失败,所以需要慎用。

# 线程间如何通讯

CountDownLatch 利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

CyclicBarrier 字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了 1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:

CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;

而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

Java并发编程:CountDownLatch、CyclicBarrier和Semaphore (opens new window)

Java 里如何实现线程间通信 (opens new window)

java线程间通信 (opens new window)

# 偏向锁、自旋锁、轻量级锁、重量级锁

# 重量级锁

内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。

# 自旋锁

内核态与用户态切换仪容仪表优化,通过自旋锁,减少线程阻塞造成的线程切换

锁粒度小,持有时间短,锁阻塞造成线程切换时间持有时间相当,减少线程切换

缺点

  • 单核不存在并行,当前不阻塞自己的话,就的不能执行,锁永远不会释放,自旋无用
  • 自旋占用CPU,
  • 锁竞争时间长,自旋无法获得,浪费时间

# 自适应自旋锁

自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定

然而,自适应自旋也没能彻底解决该问题,

# 轻量级锁

# 偏向锁

偏向锁、轻量级锁、重量级锁适用于不同的并发场景:

  • 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
  • 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
  • 重量级锁:有实际竞争,且锁竞争时间长。

如果锁竞争时间短,可以使用自旋锁进一步优化轻量级锁、重量级锁的性能,减少线程切换

image

synchronized详解 (opens new window)

浅谈偏向锁、轻量级锁、重量级锁 - 掘金 (opens new window)

Java Synchronised机制 | Jacks Blog (opens new window)

Java的对象头和对象组成详解_java_lkforce-CSDN博客 (opens new window)