线程与进程的区别
1.进程是正在运行的程序实例,进程中包含了线程,每个线程执行不同的人物
2.不同的进程使用不同的进程空间,在当前进程下的所有线程可以共享内存空间
3.线程更轻量,线程上下文切换成本一般比进程上下文切换低(上下文切换一般指从一个线程到另一个线程)
并行与并发的区别
1.并发是指两个或多个事件在同一时间间隔发生,把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
2.并行是指两个或者多个事件在同一时刻发生,把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。
四种创建线程的方式
1.继承Thread类,重写run方法
2.实现runable接口,重写run方法
3.实现callable接口,重写call方法
4.线程池创建线程
runable与callable的区别
1.runable接口run方法无返回值
2.callable接口call方法有返回值,是个泛型,和future、futuretask配合可以来获取异步执行的结果
3.callable接口的call方法允许抛出类;而runable接口的run方法的异常只能内部消化,不能继续上抛
线程的run()与start()有什么区别
start用来启动线程,通过该线程调用run方法中定义的逻辑代码。start方法只能调用一次
run方法中封装了要被线程执行的代码,可以被调用多次
线程包含哪些状态,状态之间是怎么变化的
1.NEW 新建状态
2.RUNNABLE 可执行状态
3.BLOCKED 阻塞状态
4.WAITING 等待状态
5.TIMED_WAITING 时间等待状态
6.TERMINATED 终止状态
变化:
1.新建线程是新建状态
2.调用了start()方法可以变为可执行状态
3.线程获取到了CPU的执行权,执行结束是终止状态
4.在可执行状态的过程中如果没有获取CPU的执行权,可能会切换到其他状态
a.如果没有获取锁就会进入阻塞状态,获取锁才会到可执行状态
b.如果线程调用了wait()方法就会进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
c.如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换会可执行状态
新建T1、T2、T3三个线程,如何保证他们按顺序执行
可以使用join()方法解决,join()等待线程运行结束
notify与notifyall的区别
notify:只随机唤醒一个wait线程
notifyall:唤醒所有wait线程
java中的wait和sleep方法有何不同
共同点:wait(),wait(long),sleep(long)都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
不同点:
1.方法归属不同:sleep(long)是Thread的静态方法,wait()与wait(long)都是Object的成员方法,每个对象都有
2.醒来时机不同:a.执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
b.wait(long)和wait()还可以被notify()唤醒,wait()如果不被唤醒就会一致执行下去
c.它们都可以被打断唤醒
3.锁特性不同:a.wait()方法的调用必须先获取wait对象的锁,而sleep没有这个限制
b.wait()方法执行后会释放锁,允许其他线程获得该锁对象
c.sleep如果在synchronized代码块中执行并不会释放锁
如何停止一个正在运行的线程
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程中止
2.使用stop方法强行中止
3.使用interrupt方法中断线程
synchronized关键字的底层原理
1.synchronized[对象锁]采用互斥的方式让同一时刻至多只有一个线程能持有[对象锁]
2.它的底层由moniter实现,moniter是jvm级别的对象,线程获得锁需要使用对象锁关联moniter
3.在monitor内部有三个属性,分别是owner、entrylist与waitset
4.其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是出于阻塞状态的线程;waitset关联的是出于waiting状态的线程
锁升级(锁膨胀)
指synchronized从无锁升级到偏向锁、再到轻量级锁最后到重量级锁的过程。java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有、多线程竞争锁三种情况
1.重量级锁:底层使用monitor实现,里面涉及到了用户态和内核态的切换,进程的上下文切换,成本比较高
2.轻量级锁:线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证了原子性。
3.偏向锁:一段很长的时间内都是只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获得锁,只需要判断markword中是否是自己的线程id即可,而不是开销相对较大的CAS命令
谈谈JMM(java内存模型)
1.JMM是java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
2.JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
3.线程跟线程之间是相互隔离,线程跟线程的交互需要通过主内存
CAS
1.全称是compare and swap(比较再交换);体现了一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性
2.CAS用到的地方很多:AQS框架、Atomic XXX类等
3.在操作共享变量时使用的自旋锁,效率上更高一些
4.CAS底层调用的是unsafe类中的方法,都是操作系统提供的,其它语言实现。
乐观锁和悲观锁的区别
1.CAS是基于乐观锁得思想:最乐观的估计就是不怕别的线程来修改共享变量,就算改了也没关系,重试就行
2.synchronized是基于悲观锁的思想,最悲观的估计,得提防着其他线程来修改共享变量,我上了锁你们都别想修改,我改完解开了锁你们才有机会
谈谈你对volatile得理解
1.保证线程间的可见性:用volatile修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
2.禁止进行指令重排序:用volatile修饰共享变量会在读、写共享变量时加入不同的屏障,组织其他读写操作越过屏障,从而达到阻止重排序的效果
什么是AQS
1.是多线程中的队列同步器。是一种锁机制,他是作为一个基础框架使用的,像Reentranlock、Semaphone都是基于AQS实现的。
2.AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程
3.在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中有一个线程修改成功了state就变为1,则当前线程就相当于获得了资源
ReentrantLock的实现原理
1.Reentrantlock表示支持重新进入的锁,调用lock方法获取了锁之后,再次调用lock是不会阻塞的
2.Reentrantlock主要利用CAS+AQS队列来实现
3.支持公平锁和非公平锁,在提供的构造器中无参默认是非公平锁,也可以传参设置为公平锁
Synchronized和lock有什么区别
1.语法层面:synchronized是关键字,源码在jvm中,是c++实现,lock是接口,源码由jdk提供用java语言实现,使用synchronized时,退出同步代码块锁会自动释放,而使用lock时,需要手动调用unlock方法释放锁
2.功能层面:二者都属于悲观锁,都具备基本的互斥、同步、锁重入功能,lock提供了许多synchronized不具备的功能,例如公平锁,可打断,可超时,多条件变量,lock有适合不同场景的实现,如Reentrantlock和ReentrantReadWriteLock(读写锁)等
3.性能层面:在没有竞争时,synchronized做了很多的优化,如偏向锁、轻量级锁,性能不错
,在竞争激烈时,Lock的实现通常会提供更好的性能
死锁产生的条件是什么
一个线程需要同时获取多把锁,这时候就容易发生死锁
如何进行死锁判断
1.jdk自带的工具:jps与jstack
2.jps:输出jvm中运行的进程状态信息
3.jstack:查看java进程内线程的堆栈信息,查看日志,检查是否有死锁,若有死锁现象,需要查看具体代码分析后修复
4.可视化工具jconsole、visualVM
聊一下ConcurrentHashMap
1.底层数据结构是数组+链表/红黑树
2.加锁方式:采用CAS添加新节点,采用Synchronized锁定链表或红黑树的首节点
导致并发程序出现问题的根本原因是什么
1.原子性 lock、synchronized,线程切换带来了原子性的问题
2.内存可见性 volatile、synchronized、lock,在多核CPU情况下带来了可见性的问题
3.有序性 volatile,编译器指令重排优化带来了有序性的问题
线程池的执行原理
1.任务提交:将执行任务提交到线程池。如果线程池中的线程少于核心线程数,则创建新的线程执行这个任务;如果线程数已达到核心线程数,任务会被加入到队列中等待执行
2.任务排队:如果队列也已满,且当前线程数小于最大线程数,则创建新的线程来处理被提交的任务;如果线程数已达到最大值,则根据饱和策略处理无法执行的任务
3.任务执行:线程从队列中取出任务并执行
4.线程闲置:在执行完任务后,线程不会立即销毁,而是可用于执行其他任务
5.线程销毁:如果线程超过一定时间没有任务执行,且当前线程数大于核心线程数,则这个线程会被销毁,以节省资源
线程池中有哪些常见的阻塞队列
a. LinkedBlockingQueue b. ArrayBlockingQueue
默认无界、支持有界 强制有界
底层是链表 底层是数组
是懒惰的,创建节点时添加数据 提前初始化Node数组
入队会生成新Node Node是需要提前创建好的
两把锁 一把锁
如何确定核心线程数
1.高并发、任务执行时间短—>cpu核数+1
2.并发不高、任务执行时间长—–>IO密集型(cpu核数*2+1)
计算密集型(cpu核数+1)
3.高并发、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步
线程池的种类有哪些
1.newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
2.newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按指定顺序执行
3.newCachedThreadPool:创建一个可缓存线程池,若线程池长度超过处理需要,可灵活回收空闲线程,若无可挥手,则新建线程
4.newScheduledThreadPool:可执行延迟任务的线程池,支持定时及周期性任务执行