并发和并行有什么区别?
并发强调的是同一时间间隔,并行强调的是同一时间发生。并发指的是同一台处理器在同一时间间隔下处理多个程序。而并行则是多个处理器同一时间处理多个程序
线程和进程有什么区别呢?
1.进程是运行中的一段程序。而进程中的执行的每个任务都可以作为一个线程。
2.进程拥有独立的内存单元。而线程共享内存资源。
3.线程的开销比进程的开销要小。
4.影响力不同,子进程无法影响父进程。但是子线程可以影响父线程。
创建线程有哪几种方式
继承 Thread 类创建线程类,调用线程对象的 start() 方法来启动该线程。
通过 Runnable 接口创建线程类。
通过 Callable 和 Future 创建线程,
线程安全的解决方案?
• 数据不共享,单线程可见,比如 ThreadLocal 就是单线程可见的;
• 使用线程安全类,比如 StringBuffer 和 JUC(java.util.concurrent)下的安全类
• 使用同步代码或者锁。
守护线程是什么
守护线程(即 daemon thread),是个服务线程,准确地来说就是服务其他的线程。
线程有哪些状态???
新建,就绪,运行,阻塞,死亡。
新建:在生成线程对象,还没有调用该对象的 start 方法,这是属于创建状态
就绪:有资格分到 cpu 但还没有轮到
运行:分到 cpu,能真正执行线程内代码
阻塞:没资格分到 cpu 时间的
死亡:一个线程的 run 方法结束或者调用 stop 方法后,该线程就会死亡
导致线程阻塞的方法
sleep,suspend,wait 都可以使线程进入阻塞状态
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。
wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
当前线程调用 suspend() 后后立即挂起,线程状态还是 RUNNABLE, 不会释放锁资源,这就会很容导致死锁(这也是弃用它的原因);
请描述一下线程池?
简单来说就是创建一些线程放入一个池子中,来任务了有空闲线程就分配线程,如果没有的话任
务进入队列进行等待其它任务使用后释放线程,如果队列满了的话我们就在创建一个线程,如果
创建这个线程大于最大线程数限制那么就会触发拒绝策略!(这个过程体现复用的原理)
线程池的优点
- 线程池可以实现少量线程复用执行大量任务,提高线程的利用率
- 不用重复的创建销毁,提高程序的响应速度
- 放在同一个池子中,方便统一管理
- 可以控制最大并发数
线程池的参数
(1)corePoolSize:线程池中常驻核心线程数
(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数
(3)keepAliveTime:多余的空闲线程存活时间
(4)unit:keepAliveTime的时间单位
(5)workQueue:任务队列,被提交但尚未执行的任务
(6)threadFactory:表示生成线程池中的工作线程的线程工厂
(7)handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何拒绝
谈一下悲观锁和乐观锁的区别
悲观锁的代表分别是 synchronized 和 Lock 锁
核心思想是线程占了锁才能去操作共享变量,每次只有一个线程占锁成功,获取锁失败的线程需要停下来等待线程从运行到阻塞,再从阻塞到唤醒涉及到上下文切换影响性能。实际上,线程在获取synchronized 锁和 Lock 锁时,如果锁已被占用,都会做几次重试操作,减少阻塞机会。
乐观锁的代表时 AtomicInteger,使用 cas 来保证原子性核心思想是无需加锁,每次只有一个线程能修改共享变量,其它失败线程不需要停止,不断重试直至成功。由于线程一直运行,不需要阻塞因此不涉及到线程的上下文切换但是它需要多核 cpu 的支持且线程数不应超过 cpu 核数。
lock 与 synchronized 的区别
语法不同:synchronized 是关键字,源码在 jvm 中,用 c++ 实现。Lock 是接口,源码由 jdk 提供,用 java 语言实现。使用 synchronized,退出同步代码块会自动释放,而使用 Lock 则需要手动调用 unlock 方法释放
功能方面: 两者都属于悲观锁,都具备基本的互斥,同步,锁重入功能。Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态,公平锁等等。
性能方面:在没有竞争的时候,synchronized 提供了很多优化比如偏向锁,轻量锁,性能不赖。在竞争激烈时,Lock 的实行通常会提供更好的性能
请描述一下 cas(cas 是一个什么样的同步机制)
CAS 即 compare and swap ,直译就是比较并交换。当要对变量进行修改时,先会将内存位置的值与预期的变量原值进行比较,如果一致则将内存位置更新为新值,否则不做操作,无论哪种情况都会返回内存位置当前的值!
compareAndSwap(V,A,B):(注意:三个参数也是个考点)
CAS 包含了三个参数:内存值 V,旧的预期值 A,要更新的值 B。当且仅当内存值 V 等于旧的预期值 A 时,才会将内存值 V 修改为新值 B,否则什么都不干;
cas 存在的缺陷
1.循环时间长,开销大:如果 cas 操作失败的话则要循环进行 cas 操作,如果长时间不成功的话则会造成 cpu 极大的开销。
2.只能保证一个共享变量的原子操作
3.ABA 问题:CAS 在检查值的时候,只会比较预期值 A 与内存位置的值是否相同,如果内存位置值经过若干次修改又变回了 A (A -> B -> A),CAS 检查依旧会通过,但是实际上这个值已经修改过了。
解决方案:解决的思路就是引入类似乐观锁的版本号控制,不止比较预期值和内存位置的值,还要比较版本号是否正确。
synchronized用法
- 修饰代码块
- 修饰方法
Reentrantlock
ReentrantLock 基于 AQS,在并发编程中它可以实现公平锁和非公平锁来对共享资源进行同步。Sync可以说是 ReentrantLock 的亲儿子,它寄托了全村的希望,完美的继承了 AbstractQueuedSynchronizer,是 ReentrantLock 的核心,后面的 NonfairSync 与 FairSync 都是基于 Sync 扩展出来的子类, 亦即通过二者实现了公平锁和非公平锁。new ReentrantLock() 默认创建的为非公平锁,如果要创建公平锁可以使用 new ReentrantLock(true)。
Reentrantlock 有哪些优势
- ReentrantLock 具备非阻塞方式获取锁的特性,使用 tryLock() 方法。
- 可以中断获得的锁,使用 lockInterruptibly() 方法当获取锁之后,如果所在的线程被中断,则会抛出异常并释放当前获得的锁(lock() 和 lockInterruptibly() 的区别在于获取线程的途中如果所在的线程中断,lock() 会忽略异常继续等待获取线程,而 lockInterruptibly() 则会抛出InterruptedException 异常)。
- ReentrantLock 可以在指定时间范围内获取锁,使用 tryLock(long timeout,TimeUnit unit) 方法
synchronized 和 ReentrantLock 有什么区别?
•ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
•ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等;
•ReentrantLock 性能略高于 synchronized。
AQS
AQS 是一个抽象类,它定义了一套多线程访问共享资源的同步器框架。通俗解释,AQS 就像是一个队列管理员,当多线程操作时,对这些线程进行排队管理。
AQS 主要通过维护了两个变量来实现同步机制的
- state
AQS 使用一个 volatile 修饰的私有变量来表示同步状态,当 state=0 表示释放了锁,当 state>0 表示获得锁 - FIFO 同步队列
AQS 通过内置的 FIFO 同步队列,来实现线程的排队工作。如果线程获取当前同步状态失败,AQS会将当前线程的信息封装成一个 Node 节点,加入同步队列中,并且阻塞该线程,当同步状态释放,则会将队列中的线程唤醒,重新尝试获取同步状态。
公平锁和非公平锁
公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。
非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。
自旋锁,非自旋锁
自旋锁: 是指当一个线程在获取锁失败时将一直循环等待,不断重新获取锁,直到获取到锁才会退出循环, 自旋锁会让线程一直处于用户态, 不会发生上下文切换
非自旋锁: 获取锁失败会进入阻塞状态, 从而进入内核态