1 线程
1 线程状态
java中有六种:
六种与五种的区别:
操作系统
操作系统有五种状态:
- 新建
- 就绪
- 运行
- 阻塞
- i/o阻塞 同步阻塞 等待 超市等待 - 终止
runnable:可能就绪,运行,阻塞i/o
2多线程有什么用
提高cpu利用率,防止阻塞,便于建模。
多线程和单线程的区别
多线程涉及上下文切换,不会提高程序的执行速度,但会对用户来说可以减少用户的响应时间。
3 程序,进程,线程的区别
程序:还有指令和数据的文件。静态的代码集合,保存在磁盘中。
进程:程序的一次执行,系统运行程序的基本单位。
线程:比进程更小的执行单位。
一个进程运行过程中会创建多个线程,
线程与进程最大的区别在于,进程相互之间一般是独立的,而同一进程下的线程可以相互影响,共享同一块内存资源和一组系统资源,在各线程间切换工作时,消耗比进程小得多。
4 线程的创建方式
1 继承Thread类
2 实现Runable接口
3 匿名内部类创建
4 实现callable接口,实现带返回值的线程。
调用FutureTask对象的get方法得道call方法的返回值。
5 定时器Timer
6 线程池创建
7 java8新特性stream实现
5 线程的终止方式
1 使用退出标记终止,使线程正常终止
2 调用interrupt方法中断线程。
3 使用stop方法,但是不推荐,stop方法已过期,使用会使系统逻辑不完整。
stop方法是过时的 从Java编码规则来说,已经过时的方式不建议采用
stop方法会导致代码逻辑不完整 stop方法是一种"恶意"的中断,一旦执行stop方法,即终止当前正在
运行的线程,不管线程逻辑是否完整,这是非常危险的.
Java中interrupted 和isInterruptedd方法的区别?
前者会将中断状态清除而后者不会。
6 start和run方法的区别
start会开启一个线程执行run中的方法,是异步的,run会在当前线程执行run方法,是同步的。
7 Runnable和Callable接口的区别
- 返回不同,Runable返回时void,不需要返回,Callable返回时一个泛型。
- 使用callable的接口,可以获取线程的运行结果,在长时间没有得到结果的时候中断线程,非常有用。
8什么是上下文切换
上下文切换就是cpu将时间片分配到另一个就绪的线程,需要保存当前线程的运行位置,同时需要加载需要恢复线程的环境信息
9 用户线程和守护线程什么区别,怎么设置
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
守护线程相当于后台管理者,负责内存清理,垃圾回收,通过setDaemon设置,必须在Thread.start之前设置。
10 线程优先级
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度
的实现,这个实现是和操作系统相关的(OSdependent)。
可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是
一个int变量(从1-10),1代表最低优先级,10代表最高优先级。
11 什么是线程调度和时间分片
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦创建一个线程并启
动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级
或者线程等待的时间。
12 线程类的构造方法、静态块是被哪个线程调用的
构造方法,静态块由当前线程,run方法由新建线程。
13 线程中如何处理异常
设置未捕获异常处理器
- 首先,它查找线程对象的未捕获异常处理器。
- 如果找不到,JVM继续查找线程对象所在的线程组(ThreadGroup)的未捕获异常处理器。
- 如果还是找不到,如同本节所讲的,JVM将继续查找默认的未捕获异常处理器。
- 如果没有一个处理器存在,JVM则将堆栈异常记录打印到控制台,并退出程序。
2 线程池
1 核心参数问题
new ThreadPoolExecutor()
1 corePoolSize 核心线程数
2 maxiumPoolSize
3 keepAliveTime
4 Unit
5 workQueue 5-3-4 执行最新的5,然后3,然后4
6 threadFacory
7 handler
AbortPolicy:直接报异常,但是程序继续在运行
CallerRunsPolicy: 让调用者自己运行,自己忙不过来了
DiscardPolicy:直接丢弃
DiscardOldestPolicy:丢弃最老的,最先加入的
2 Executors
- FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。
当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在
一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 - SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程
池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 - CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数
量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新
的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复
用。
Executors的弊端
- FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为
Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 - CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为
Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
Executor、ExecutorService、Executors的区别?
Executor 和 ExecutorService 这两个接口主要的区别是
- ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
- Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个
Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable
接口的对象。 - Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,
而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。 - Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService
还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。
Executors 类提供工厂方法用来创建不同类型的线程池。
newSingleThreadExecutor() 创建一个只有一个线程的线程池,
newFixedThreadPool(int
numOfThreads)来创建固定线程数的线程池,
newCachedThreadPool()可以根据需要创建新的线程,
但如果已有线程是空闲的会重用已有线程。
3 线程池的作用
1 提高响应速度。
2 提高资源利用率。
3 监控线程的状态。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系
统的稳定性,使用线程池可以进行统一的分配,调优和监控。
4 如果你提交任务时,线程池队列已满,这时会发生什么
如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队
列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务
如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,
ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处
理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了
的任务,默认是AbortPolicy
3 wait和sleep和yield
1 wait和sleep
共同点:都让当前线程放弃cpu状态,线程进入阻塞状态
不同点:
- 方法归属不同:sleep属于thread,wait属于object
- 醒来时机不同: sleep(long),wait(long) 在一定时间点会唤醒,wait()如果没有notify会一致阻塞下去
- 锁特性不同:
wait方法必须调用对象的锁,在锁内运行,sleep不需要。
wait方法执行完后会释放锁,允许其他线程获得该对象锁。(我放弃你们可以用)
sleep方法执行完不会释放锁(我放弃了你们也用不了)
2 sleep和yield的区别
- 优先级不同,sleep完后所有线程都有机会,yield会同级或更高级线程运行
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
- sleep进入阻塞状态,yield进入就绪状态
- sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性
3 Thread.sleep(0)的作用是什么
Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让
某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配
时间片的操作,这也是平衡CPU控制权的一种操作。
cpu重新分配时间片给线程。
4 Lock VS synchronized
语法:
- synchronized 是关键字,源码在jvm里,由c++实现
- Lock是接口,源码由jdk提供,java实现
- 使用synchronized 同步代码块执行完会自动取消,使用Lock会调用Lock.unlock()
功能:
- 均属于悲观锁,具有锁的特性:互斥,同步,锁重入等。
- Lock提供很多synchronized不具备的功能,如公平锁,可超时,可打断,获取等待状态,多条件变量。
性能:
- 没有竞争的情况下,synchronized做了很多优化,如偏向锁,轻量级锁,性能略好。
- 有竞争的情况下,Lock性能更高
1什么是AQS
AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。
java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,
ReentrantLock、CountDownLatch、Semaphore等等都用到了它。
结构 : state owner 阻塞队列 等待队列·
2 synchronized 关键字
多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为
JVM中,字符串常量池具有缓存功能!
3 synchronized底层原理
- 同步语句块
monitorenter 和 monitorexit 指令
当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行
monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等
待,直到锁被另外一个线程释放为止。 - 方法
通过ACC_SYNCHRONIZED 标识来确定
4 什么是可重入锁(ReentrantLock)?
ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁
投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换
句话说,当许多线程都想访问共享资源时,JVM可以花更少的时候来调度线程,把更多时间用在执行线
程上。)
简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加
1,然后锁需要被释放两次才能获得真正释放。
5 ReadWriteLock是什么
ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,
实现了读写的分离,读锁是共享的,写锁是独占的,
6 CyclicBarrier和CountDownLatch的区别
CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这
个点,所有线程才重新运行;
CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行
CyclicBarrier 只能唤起一个任务, CountDownLatch 可以唤起多个任务
5 voliate
Volatile 一般用于 状态标记量 和 单例模式的双检锁
保证修改可见性,保证有序性
可见性: while问题,因为jit代码优化,所以需要voliate保证,可以通过运行时命令-Xint让其不经过jit优化
有序性: 通过内存屏障实现。
1 voliate能否保证线程安全?
不能,线程安全要满足可见性,有序性,原子性。voliate不满足原子性,需要与cas结合才能做到。
2 voliate和synchronized的区别
- 本质不同:
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 - 作用范围不同:
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。 - 线程安全不同:
volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可
见性和原子性。 - 阻塞不同:
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。 - Jit优化:
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
volatile的一个重要作用就是和CAS结合,保证了原子性。
3 volatile 变量和 atomic 变量有什么不同
Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。
AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的
进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
4 java内存模型
- Java内存模型将内存分为了主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内
存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自
己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的
那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去 - 定义了几个原子操作,用于操作主内存和工作内存中的变量
- 定义了volatile变量的使用规则
- 定义了先行发生原则。定义了操作A必然先行发生于操作B的一些规则
5 notify和notifyall的区别
void notify(): 唤醒一个正在等待该对象的线程。
void notifyAll(): 唤醒所有正在等待该对象的线程。
notify可能会导致死锁,而notifyAll则不会
任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码
为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视
器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
6 线程调度相关的方法
- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理
InterruptedException异常; - notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待
状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; - notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞
争,只有获得锁的线程才能进入就绪状态;
6 乐观锁vs悲观锁
悲观锁:
代表:synchronized,Lock
核心思想:线程只有有了锁才能操作共享变量,每次只有一个线程占有锁,其他线程进入阻塞状态
缺点:频繁切换上下午,影响性能
实际上,在锁已经占用的时候回多尝试几次,都不行才会阻塞
乐观锁:
代表:AtomicInteger 底层:cas实现
核心思想:不需要加锁,每次只能有一个线程修改共享变量,其他线程会修改失败然后重复尝试修改。直到成功
线程会一直运行,不需要阻塞,不涉及上下文切换。
但是需要多核cpu支持,且线程数不超过cpu数量
1 什么是CAS
CAS,全称为Compare and Swap,即比较-替换。
假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会
将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才
能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会
变的值A,只要某次CAS操作失败,永远都不可能成功。
2 CAS缺陷
1 ABA问题
2 循环时间长开销问题
3 只能保证一个变量的原子操作
3 什么是自旋
很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是
一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。
既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的
边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的
策略。
自旋锁的优缺点?
自旋锁的效率远高于互斥锁。
但是:
1、自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
2、试图递归地获得自旋锁会引起死锁。
7 concurrentHashMap
1 对比下hashtable和concurrentHashMap实现线程安全的区别
都是线程安全的
- Hashtable并发低,只有一把锁。 扩容 2n+1,不需要二次hash。用的质数
1.8之前 concurrentHashMap 采用segment+数组+链表,每个segment对应一把锁,如果多个线程访问不同的segment则不会冲突
1.8之后将数组的每个头结点当做锁,多线程访问头结点不同不会产生冲突
8 ThreadLocal
实现【资源对象】的线程隔离,让每个线程各用各的【资源对象】,避免引发线程安全问题。
重点:线程间实现对象隔离,线程内实现资源共享。
原理:
每个线程都有一个ThreadLocalMap
类型的成员变量,用来存储资源对象。
调用set :以threalocal
对象为key,资源对象作为map存储在threadlocalmap中
调用get: 以threadlocal
作为key,到当前线程中查找关联的资源值。
调用remove方法:以threadlocal
自己作为key,移除当前线程相关联的资源。
1 key为何设计成弱引用
引用关系分为强弱虚软。
使用弱引用是为了防止自己没有好的编程习惯将key释放,导致当线程持续存在时,key一直在导致内存泄漏。
什么时候回收
get时,回去key为null
set key时,会使用启发式扫描,清除临近的key。
remove:推荐,在threadlocal对象使用完成后调用remove方法释放。
ThreadLocalMap的key没了,value还在,这就会「造成了内存泄漏问题」
2 应用场景
sessio会话管理,数据库连接
9 线程安全
线程安全通俗来讲就是多线程下运行的结果会和程序单线程运行结果一致,不会产生不确定的结果。
1 如何保证线程安全
- 使用线程安全的类
- 通过锁实现同步和互斥
- 多线程情况下,线程中共享的变量改为方法级的局部变量。
2 什么是竞态条件,怎样发现和解决竞争
当多个线程竞争统一资源时候,对资源的访问顺序敏感称为竞态条件。
在临界区使用同步的方法解决同步,可用synchronized或Lock显示锁。
3 线程安全的级别
- 不可变
- 无条件线程安全
- 有条件线程安全
- 线程兼容
- 线程对立
4 同步方法和同步块,哪个是更好的选择
同步方法块,同步方法块外的代码仍旧能并发执行,有更好的效率
**总结:**同步的范围越小越好
5 锁粗化
锁粗化的优化方法,这种方法就是把同步范围变大。
StringBuffer,它是一个线程安全的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味着要进行反复的加锁->解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁–>解锁的次数,有效地提升了代码执行的效率
6 线程如何按照顺序执行
// 引用t1线程,等待t1线程执行完
t1.join();
7 什么是阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的相互影响。
比如一个线程占用了临界区资源,那么其他所有需要这个而资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。
此时,如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。
非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行。所有的线程都会尝试不断前向执
行
8 举例说明同步和异步。
如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被
另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取
(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行
的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往
更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。
9 如何控制某个方法允许并发访问线程的大小?
Semaphore
- acquire
- release
10 高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
**高并发、任务执行时间短,**使用cpu核数+1,减少线程上下文切换
并发不高、任务执行时间长
IO密集型:增加线程数量
计算密集型:cpu核心数+1
11 如何在两个线程之间共享数据
线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和
等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的
12 Hashtable的size()方法中明明只有一条语句"return count",为什么还要做同步?
同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访
问。
CPU执行代码,执行的不是Java代码,这点很关键,一定得记住。Java代码最终是被翻译成机器码执
行的,机器码才是真正可以和硬件电路交互的代码。即使你看到Java代码只有一行,甚至你看到Java代
码编译之后生成的字节码也只有一行,也不意味着对于底层来说这句语句的操作只有一个。
10 数据分析
1 如何获取到线程dump文件
死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就
是线程堆栈,获取到线程堆栈有两步:
1、获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java
2、打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid