# 多线程
- 并行和并发有什么区别?
- 线程和进程的区别?
- 说一下 runnable 和 callable 有什么区别?
- 线程有哪些状态?
- sleep() 和 wait() 有什么区别?
- notify()和 notifyAll()有什么区别?
- 线程的 run()和 start()有什么区别?
- 创建线程池有哪几种方式?
- 线程池都有哪些状态?
- 线程池中 submit()和 execute()方法有什么区别?
- 在 Java 程序中怎么保证多线程的运行安全?
- 多线程锁的升级原理是什么?
- 什么是死锁? 怎么防止死锁?
- ThreadLocal 是什么?有哪些使用场景?
- 说一下 synchronized 底层实现原理?
- synchronized 和 volatile 的区别是什么?
- volatile和synchronized的区别
- synchronized 和 ReentrantLock 区别是什么?
- 说一下 atomic 的原理?
- 线程间如何通讯
- 偏向锁、自旋锁、轻量级锁、重量级锁
# 并行和并发有什么区别?
第十六章、垃圾回收相关概念 | 编程手册 (opens new window)
# 线程和进程的区别?
参考:
线程与进程,你真的清楚吗?_CSDN资讯-CSDN博客 (opens new window)
进程是操作系统分配资源的单位,线程是调度的基本单位,线程之间共享进程资源
线程就是函数的调用流程,每个线程由自己的栈帧
每个线程都有自己独立的、私有的栈区。
所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器是线程私有的。
# 线程间共享的资源
# 代码区域
# 数据区
C语言中的全局变量
# 堆区
new出来的对象
# 动态链接库
源码生成可执行文件,
静态链接:一股脑生成一个exe文件
动态链接:动态链接的意思是我们不把动态链接的部分打包到可执行程序,而是在可执行程序运行起来后去内存中找动态链接的那部分代码,
# 文件
# 说一下 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)
# 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)
# 线程冲突的列子
# 多线程锁的升级原理是什么?
# 什么是死锁? 怎么防止死锁?
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的区别
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- 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时间片在多个线程间切换,即可保证原子性。
# 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中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。
# 自旋锁
内核态与用户态切换仪容仪表优化,通过自旋锁,减少线程阻塞造成的线程切换
锁粒度小,持有时间短,锁阻塞造成线程切换时间持有时间相当,减少线程切换
缺点
- 单核不存在并行,当前不阻塞自己的话,就的不能执行,锁永远不会释放,自旋无用
- 自旋占用CPU,
- 锁竞争时间长,自旋无法获得,浪费时间
# 自适应自旋锁
自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
然而,自适应自旋也没能彻底解决该问题,
# 轻量级锁
# 偏向锁
偏向锁、轻量级锁、重量级锁适用于不同的并发场景:
- 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
- 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
- 重量级锁:有实际竞争,且锁竞争时间长。
如果锁竞争时间短,可以使用自旋锁进一步优化轻量级锁、重量级锁的性能,减少线程切换。
synchronized详解 (opens new window)
浅谈偏向锁、轻量级锁、重量级锁 - 掘金 (opens new window)