线程与进程的区别
什么叫做进程:当一个程序被允许,从磁盘加载到这个程序的代码至内存,这时就开启了一个进程。
什么叫做线程:线程就是一个指令流,将指令流中的一条条指令以一定顺序交给CPU执行。
两者区别:
- 进程是正在运行程序的实例,进程中包含了多个线程,每个线程执行不同的任务。
- 不同的进程使用不同的内存空间,当前进程下的所有线程可以共享内存空间。
- 线程比进程更轻量,线程上下文切换成本一般比进程低(上下文切换指的是一个线程切换到另一个线程)
并行和并发的区别
现在都是多核CPU,在多核CPU下
并发:同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU。
并行:同一时间动手做多见事情的能力,4核CPU同时执行4个线程。
创建线程的方式有哪些
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池创建线程(项目中常用的方式)
追加->runnable与callable有什么区别?
1.runnable没有返回值,callable的返回值是泛型。
2.runnable不能向上抛出异常,只能内部消化,callable可以抛出异常
3.callable可以用Future、FutureTask获取线程返回值。
追加->线程的run和start有什么区别?
run相当于一个普通方法,可以在一个线程中多次执行,而start是开启一个线程,一个线程只能开启一次,多次调用就会抛出异常。
线程包括哪些状态,状态之间如何变换
线程的状态可以参考JDK中的Thread类中的枚举State。
有6种状态分别为:新建(new)、可执行(runnable)、阻塞(blocked)、等待(waiting)、时间等待(timed_waiting)、终止(terminated)
线程状态之间的变化
- 创建线程对象是新建状态。
- 调用start()方法转变为可执行状态。
- 线程获取到了CPU的执行权,执行结束是终止状态。
- 在可执行状态的过程中,如果没有获取到CPU的执行权,可能会切换其它的状态。
- 如果没有获取到锁(synchronized或lock)进入阻塞状态,获得锁再切换为可执行状态。
- 如果线程调用了wait()方法进入等待状态,其它线程调用notify()唤醒可切换为可执行状态。
- 如果线程调用了sleep()方法,进入计时等待状态,到时间后可切换为可执行状态。
新建T1、T2、T3三个线程,如何保证它们按顺序执行
可以使用线程中的join方法解决。
notify和notifyAll有什么区别
notify:只随机唤醒一个wait方法。
notifyAll:唤醒所有wait方法。
在java中wait和sleep()方法有什么区别
共同点:
wait()、wait(long)和sleep的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。
不同点:
1.方法的归属不同
- sleep(long)是Thread的静态方法。
- 而wait()、wait()都是Object的成员方法,每个对象都有。
2.醒来的时机不同
- 执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来。
- wait(long)和wait()还可以别notify唤醒,wait()如果不唤醒就一直等下去。
- 它们都可以被打断唤醒。
3.锁的特性不同(重点)
- wait()方法的调用必须先获取wait对象的锁,而sleep则无此限制。
- wait()方法执行后会释放锁对象,允许其它线程获得该对象锁(我放弃cpu,但你们还可以使用)。
- 而sleep如果在synchronized代码块中执行,并不会释放对象锁(我方法cpu,你们也用不了)。
如何停止一个正在运行的线程
- 使用退出标志,使线程正常的退出,也就是run方法完成后线程终止。
- 使用stop强行终止(不推荐,方法作废)
- 使用interrupt方法中断线程。
- 打断阻塞的线程(join、wait、sleep),线程会抛出异常。
- 打断正常的线程,可以根据打断状态来标记是否退出线程。
synchronized关键字的底层原理
基本使用方式
Synchronized[对象锁]采用互斥的方式让同一时刻至多只有一个线程能持有[对象锁],其它线程想要获取这个[对象锁]就会堵塞
Monitor->被翻译为监听器,由jvm提供,c++语言实现。- Synchronized[对象锁]采用互斥的方式让同一时刻至多只有一个线程能持有[对象锁]。
- 它的底层由monitor实现的,monitor是jvm级别的对象(C++实现),线程获得锁需要使用对象(锁)关联monitor。
- 在monitor内部有三个属性,分别是owner、entrylist、waitset。
- 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于waiting状态的线程。
- 进阶->Monitor实现的锁属于重量级锁,你了解过锁升级吗?
谈谈JMM(Java内存模型)
JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
- JMM(Java内存模型),定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
- JMM把内存分为两块,一块是私有线程的工作区(工作内存),一块是所有线程的共享区域(主内存)。
- 线程跟线程中间是相互隔离,线程跟线程交互需要通过主内存。
CAS你知道吗?
-
CAS的全称是:Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。
-
CAS使用的地方很多:AQS框架、Atomicxxx类。
-
在操作共享变量的时候使用的自旋锁,效率上更高。
-
CAS的底层是调用的Unsafe类中的方法,都是操作系统提供的,其它语言实现。
追问—>乐观锁和悲观锁的区别
-
CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃点亏再重试。
-
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解锁,你们才有机会。
谈谈你对volatile的理解
- 保证线程间的可见性:用volatile修饰的共享变量,能够防止编译器优化发生,让一个线程对共享变量的修改对另一个线程可见。
- 禁止指令重排序:用volatile修饰共享变量会在读、写共享变量时加入不同屏障,阻止其它读写操作越过屏障,从而达到阻止重排序的效果。
什么是AQS
全称是 AbstractQueuedSynchronizer,即抽象队列同步器。它是构建锁或者其他同步组件的基础框架。
AQS与Synchronized的区别
- 是多线程中的队列同步器。是一种锁机制也是非公平的,它是做为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的。
- AQS内维护了一个先进先出的双向队列,队列中存储的排队的线程。
- AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中的有一个线程修改成功了state为1,则当前线程就等于获取了资源。
- 在对state修改的时候使用cas操作,保证多个线程修改的情况下原子性。
ReentrantLock的实现原理
ReentrantLock翻译过来是可重入锁,相对于synchronized它具备一下特点:
- 可中断
- 可以设置超时时间
- 可以设置公平锁
- 支持多个条件变量
- 与synchronized一样,都支持重入
原理: - ReentrantLock表示支持重新进入的锁,调用lock方法获取锁之后,再次调用lock,是不会再阻塞
- ReentrantLock主要利用CAS+AQS队列来实现
- 支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可以传参设置为公平锁。
synchronized和Lock有什么区别
- 语法层面
synchronized是关键字,源码在jvm中,用c++语言实现。
Lock是接口,源码由jdk提供,用java语言实现
使用synchronized时,退出同步代码块锁会自释放,而使用Lock时,需要手动调用unlock方法释放锁
- 功能层方面
二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
Lock提供了许多synchronizde不具备的功能,例如公平锁、可打断、可超时、多条件变量
Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock(读写锁)
- 性能层面
没有竞争时,synchronized做了很多优化,如偏向锁、轻量级锁,性能不赖。
在竞争激烈时,Lock的实现通常会提供更好的性能。
死锁产生条件是什么
死锁:一个线程需要同时获取多把锁,这时就容易发生死锁。
例如:线程t1持有A的锁等待获取B锁,线程t2持有B的锁等待获取A的锁。
如何进行死锁诊断
当程序出现死锁现象,我们可以用jdk自带的工具:jps和jstack
- jps:输出JVM中运行的进程状态信息。
- jstack:查看java进程内线程的堆栈信息。
其它解决工具
聊一下ConcurrentHashMap
- 底层数据结构
- jdk1.7底层数据结构是数组+链表。
- jdk1.8采用的数据结构跟HashMap1.8一样的结构,数组+链表/红黑二叉树。
- 加锁的方式
- jdk1.7采用Segment分段锁,底层使用的ReentrantLock。
- jdk1.8采用的CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段速锁粒度更细,性能更好。
导致并发程序出现问题的根本原因是什么
Java并发编程三大特性:
- 原子性:一个线程在CPU中操作不可以暂停,也不可以中断,要不执行完成,要不不执行。
- synchronized:同步加锁
- JUC里面的lock:加锁
- 内存可见性:一个线程对共享变量的修改对另一个线程可见。
- volatile
- lock
- synchronzide
- 有序性:指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一直,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
- volatile
线程池的核心参数(线程池的执行原理)
线程池核心参数
线程池执行流程
线程池中有哪些常见的阻塞队列
如何确定核心线程数
先了解
线程池的种类有哪些
在java.until.concurrent.Executors类中提供了大量创建连接池的静态方法,常见的有4种。
- newFixedThreadPool:创建使用固定线程数的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO)执行。
- newCachedThreadPool:创建一个可以缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性执行。
为什么不建议使用
线程池的使用场景
场景一
流程图
场景二
场景三
- 批量导入:使用线程池——CountDownLatch批量把数据库中的数据导入到了ES(任意)中,避免OOM。
- 数据总汇:调用多个接口来汇总数据,如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能。
- 异步线程(线程池):为了避免下一级方法影响上一级方法(性能考虑),可以使用异步线程调用下一个方法(不需要下一级方法返回值),可以提升方法响应时间。
如何控制每个方法允许并发访问线程的数量
在多线程中提供了一个工具类Semaphore,信号量。在并发的情况下,可以控制方法的访问量。
- 创建Semaphore对象,可以给一个容量。
- acquire()可以请求一个信号量,这时候的信号量个数-1。
- release()释放一个信号量,此时信号量个数+1。
谈谈你对ThreadLocal的理解
ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变
量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享。