1_线程和进程的区别?
- 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
- 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同进程的线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
- 包含关系:进程包含线程,一个进程至少有一个线程
内存分配:同一进程的不同线程可以共享本进程的地址空间和资源,而进程之间的地址空间和资源是独立的 - 影响关系:一个进程崩溃不会影响其他进程,一个线程崩溃整个进程都要死掉
- 执行过程:每个独立的进程有程序运行的入口.顺序执行序列和程序出口。但是线程不能独立执行,必 须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
创建线程的3种方式? -书栈
- 继承thread,重写run()方法
- 实现runnable接口,重写run()方法,生成一个实例,在创建Thread的时候传入这个实例
- 实现callable接口,重写call()方法,生成一个实例,在创建Thread的时候传入这个实例
3_线程的状态流转?
new(新建):当线程对象被创建后,即进入了新建状态.
runnable(就绪):当调用线程对象的start()方法,线程即进入就绪状态。
running(运行):当线程开始调度处于就绪状态的线程时,进入运行状态。
block(阻塞):处于运行状态中的线程由于某种原因,暂时放弃对cpu的使用权,停止执行,此时进入阻塞状态.
dead(死亡):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
5_什么是线程死锁,如何避免死锁?- 书栈
java内存模型
在Java虚拟机中,变量的值保存在主内存中,但是,当线程访问变量时,它会先获取一个副本,并保存在自己的工作内存中。如果线程修改了变量的值,虚拟机会在某个时刻把修改后的值回写到主内存,但是,这个时间是不确定的!
volatile
每次访问变量时,总是获取主内存的最新值;
每次修改变量后,立刻回写到主内存。
daemon Thread
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。守护线程不能持有需要关闭的资源(如打开文件等)。
可重入锁
JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。
什么是线程死锁
两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁
如何避免死锁
线程获取锁的顺序要一致
6_Runnable 和 Callable 对比
- Callable 规定(重写)的方法是call()方法, runnable 规定(重写)的方法是run()方法。
- call()方法有返回值,而run()方法没有返回值。
- call()方法可以抛出异常,run()方法不可以。
6_shutdown 和 shutdownNow
shutdown():方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。
shutdownNow(): 会立刻停止正在执行的任务。
awaitTermination():会等待指定的时间让线程池关闭。
7_sleep 和 wait
相同
两者都会使线程进入阻塞状态
不同
- sleep 方法是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态,当睡眠时间到了,会解除阻塞,进入可运行状态。如果有的话,睡眠不释放锁。
- wait 方法是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify 或者notifyAll被调用后,会解除阻塞。但是,只有重新持有互斥锁之后才会进入可运行状态。等待时,会释放互斥锁。
8_为什么我们调用start()方法时会调用run()方法,为什么我们不能直接调用run()方法
- new 一个 Thread线程进入了new状态; 调用start 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,(调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。)这是真正的多线程工作。
- 直接执行run 方法,并不会开启新的线程中执行它。
12_线程阻塞的三种情况
- 等待阻塞, running状态下的线程,执行wait方法后,jvm会将线程放入等待队列(wait queue)。
- 同步阻塞, running状态下的线程,在获取对象的同步锁时,若同步锁被其他的线程占用,则JVM将该线程放入锁池(lock pool)中。
- 其他阻塞, running状态下的线程, 执行sleep或者join方法,或发出I/O请求时,JVM会将该线程设置为阻塞状态。当sleep超时,join等待线程终止超时,或I/O处理完毕时,线程重新转入可运行状态。
13_线程死亡的三种方式
- 正常结束, run、call方法执行完毕,线程正常结束。
- 异常结束, 线程抛出一个未捕获的exception 或 error,导致线程异常结束。
- 调用stop结束,一般不推荐这样使用,因为有可能会导致死锁。
19_synchronized 和 volatile 的区别是什么?
- volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读 取; synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile 仅能使用在变量级别 ; synchronized 则可以使用在 变量、方法、类级别。
- volatile 仅能实现变量的修改可见性,不能保证原子性; 而 synchronized 则可以保证变量的修改可见性和原子性。
- volatile 不会造成线程的阻塞 ; synchronized 可能造成线程的阻塞。
- volatile 标记的变量不会被编译器优化; synchronized 标记的变量可以被编译器优化。
20_synchronized 和 lock 的区别是什么?
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁; 而 lock 需要自己加锁和释放锁,如果使用不当没有 unlock() 去释放锁就会造成死锁。
- 通过 lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
21_synchronized 和 ReentrantLock 的区别是什么?
-
两者都是可重入锁
可重入锁:重入锁,也叫做递归锁,可重入锁指的是在一个线程中可以多次获取同一把锁,比如: 一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行 调用的方法,而无需重新获得锁, 两者都是同一个线程每进入一次,锁的计数器都自增,所以要等到锁的计数器下降为时才能释放锁。 -
synchronized 依赖与JVM 而 ReentrantLock 依赖与API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 jdk1.6 为 synchronized 关 键字进行了很多优化,但是这些优化都是在虚拟机层面实现的
ReentrantLock 是 jdk 层面实现的(也就是 api 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成) -
ReentrantLock 比 synchronized 增加了一些高级功能
主要来说主要有三点:1等待可中断;2可 实现公平锁;3可实现选择性通知(锁可以绑定多个条件)
- 等待可中断,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- ReentrantLock 可以指定是公平锁还是非公平锁。而 synchronized 只能是非公平锁。所谓的公平锁 就是先等待的线程先获得锁。 ReentrantLock 默认情况是非公平的,可以通过 ReentrantLock 类的 ReentrantLock(boolen fair) 构造方法来制定是否是公平的。
- ReentrantLock 类线程对象可以注册在指定的 Condition 中,从而可以有选择性的进行线程通知,在 调度线程上更加灵活。 在使用 signal/signalAll 方法进行通知时,被通知的线程是由 JVM 选择 的,使用 ReentrantLock类结合Condition实例可以实现“选择性通知”。
- 选择使用
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。
26_synchronized为什么是非公平锁?非公平锁体现在哪些地方?
synchronized 的非公平其实在源码中应该有不少地方,因为设计者就没按公平锁来设计,核心有以下几 个点:
- 当持有锁的线程释放锁时,该线程会执行以下两个重要操作:
- 先将锁的持有者 owner 属性赋值为 null
- 唤醒等待链表中的一个线程(假定继承者)。
在唤醒等待线程的时候,如果有其他线程刚好在尝试获取锁(例如自旋),则可以马上获取到锁。
- 当线程尝试获取锁失败,进入阻塞时,放入链表的顺序,和最终被唤醒的顺序是不一致的,也就是说 你先进入链表,不代表你就会先被唤醒。
32_了解 reentrantLock 吗?
reentrantLock 是一个可重入的独占锁,主要有两个特性,一个是支持公平锁和非公平锁,一个是可重入。
reentrantLock 实现依赖于 AQS
reentrantLock 主要依靠AQS维护一个阻塞队列,多个线程对加锁时,失败则会进入阻塞队列(锁池?)。等待唤醒,重新尝试获取锁。
33_ReadWriteLock是什么?
首先 reentrantLock 某些时候有局限,如果使用 reentrantLock 可能本身是为了防止线程A在写数据、 线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。
因为这个,才诞生了读写锁 ReadWriteLock ReadWriteLock 是一个读写锁接口, ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能
线程池_1_为什么使用线程池
使用线程池的好处:
- 降低资源消耗,通过重复利用已经创建的线程来降低线程创建和销毁造成的消耗。
- 提高响应速度,当任务到达时,任务可以不需要等待线程创建而直接执行。
- 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性。使用线程池可以进行统一的分配,调优和监控。
线程池_2_执行excute和submit方法的区别是什么
- excute() 方法用于提交不需要返回值的任务。
- submit() 方法用于提交需要返回值的任务。
线程池_3_说一下线程池核心参数
- 核心线程数/corePoorSize, 线程池一直运行,核心线程就不会停止
- 最大线程数/maxPoorSize
- 非核心线程的心跳时间/keepAliveTime, 如果非核心现在在心跳时间内没有任务执行,非核心线程就会消亡。
- 阻塞队列/workQueue, 用来存放线程任务
- 饱和策略/defaultHandler,4种
- 线程任务丢弃报错。默认饱和策略
- 线程任务直接丢弃不报错。
- 将阻塞队列首任务丢弃,将最新线程任务重新加入队列执行。
- 线程池之外的线程直接调用run方法执行。
- 线程工厂/ThreadFactory
线程池执行任务的流程
要执行的任务数< 核心线程数,则不会进入队列,而是创建新的线程
要执行的任务数 > 核心线程数,并且等待队列未满,则进入等待队列;
要执行的任务数 > 核心线程数,并且 < 最大连接数 并且 等待队列已满,则创建新线程;
要执行的任务数 > 核心线程数,并且 > 最大连接数 并且 等待队列已满,则调用拒绝策略来处理该任务(抛异常)
线程池_5_常见的java线程池有哪几种
FixedThreadPool:线程数固定的线程池;
CachedThreadPool:线程数根据任务动态调整的线程池;
SingleThreadExecutor:仅单线程执行的线程池。