1.并行与并发
并行是指同一时间执行多条指令
并发是指同一时间线程轮流使用CPU ,轮流执行指令
2.创建线程池的四种方式
1.继承Thread类
2.实现Runnable接口(无返回值)
3.实现Callable接口(有返回值)
4.通过线程池创建线程
3.Run() 和 Start() 的区别
1.run调用同一个线程
2.start每次都开启一个新的线程进行调用
4.线程包括哪些状态,状态之间是如何变化的
线程总共有六种状态,新建,可运行, 终结 ,阻塞,等待(wait) ,有时限 (sleep)
我简单介绍一下状态是如何切换的
在一个线程新建但是没有调用start方法的时候 是处于新建状态的, 如果调用了start方法,就会进入到可运行状态,执行完代码 进行到终结状态 。 这是一个线程正常情况下的执行流程 。
阻塞是当线程获取锁失败之后 进入到阻塞状态,只有当持有线程的锁释放锁的时候,他会按照一定的规则去获取锁,获取后变成可运行状态。
等待是线程获取锁后,调用了wait方法 此时进入等待状态,当其他线程调用notify或者notifyall方法后,会恢复成可运行状态
有时限是线程获取锁后,调用了sleep方法,此时进入有时限状态,不需要主动去唤醒他,等睡眠时间结束后就会自然恢复成可运行状态。
5.wait和sleep的区别
wait是Obejct的成员方法,每个对象都有
sleep是thread 的静态方法。
wait需要被唤醒, sleep自然醒来
wait方法执行后会释放对象锁,而sleep方法执行后不会释放对象锁
6.Synchronized锁的理解(非公平锁-悲观锁)
注: Monitor(监视器) 由jvm提供 c++实现
Synchronized 是采用一种互斥锁的方式,让同一时刻只有一个线程可以持有对象锁
Synchronized的底层采用的是monitor ,monitor 是jvm提供的 ,由c++实现, 线程获取锁需要使用对象锁关联monitor ,
在monitor的内部由三个属性, 分别是owner ,entrylist ,waitlist
owner是获得锁的线程 ,并且只能关联一个, entrylist里面关联的是处于阻塞状态的线程, waitlist里面关联的是处于等待状态的线程
monitor属于重量级锁,它涉及到了用户态和内核态的切换,进程的上下文切换,性能是比较低的。
所以在jdk1.6版本的时候 引入了两种新型的锁机制,分别是偏向锁和轻量级锁 ,
然后我来介绍一下Synchronized锁的升级过程
(偏向锁-可重入)在锁只被一个线程持有的时候,并且很长一段时间都只有这一个线程在使用锁,那么这个锁就是一个偏向锁,它只会在第一次获取锁的时候进行一个CAS 操作, 之后再获取锁,只需要判断mark word 里面是否为自己的线程id即可,而不是每次都进行CAS操作
(轻量级锁-可重入)线程加锁的时间是错开的,没有竞争的时候,可以使用到轻量级锁,轻量级锁修改了对象头的锁标志,每次都只进行了CAS操作 ,保证了原子性,相比重量级锁,它的性能提高很多
(重量级锁)底层采用monitor实现,涉及到了用户态和内核态的切换,进程的上下文切换,性能比较低。
标注: 对象的内存结构
7.JMM (Java Memory Model)Java内存模型
每一个线程有一个工作内存,工作内存中由一个共享变量的副本
在主内存中存在共享变量。
线程对变量的所有操作都必须在工作内存当中完成,不能直接操作主内存当中的数据。线程之间的值传递需要通过主内存完成。
8.CAS (Compare And Swap)比较再交换
是一种乐观锁的思想,在无锁状态下保证了线程操作数据的原子性 。比如当两个线程去操作共享变量副本,在第一个线程操作完之后,第二个线程想继续操作的时候,就会先去比较自己存的共享变量的值和主内存当中的值是否一致,一致的话再执行自己的操作,不一致的话就会将数据更新成主内存当中的数据,再进行对数据的运算。
9.AQS(AbstractQueuedSynchronizer)是一个抽象类
它是构建锁或者其他同步组件的基础框架
常见的实现类:
1.ReentrantLock 阻塞式锁 (默认非公平锁)
2.Semaphore 信号量
3.CountDownLatch 倒计时锁
工作机制:
1.在AQS中使用到了一个volatile 修饰的 state 属性来表示资源的状态,0代表无锁, 1代表有锁
2.提供了一个基于FIFO(First Input First Output)的等待队列 ,类似于Monitor的entrylist
3.条件变量来实现等待,唤醒机制,支持多个条件变量,类似于Monitor的WaitSet
多个线程如何保证原子性
在去修改state状态的时候,使用到了cas自旋锁来保证原子性。 修改失败的线程会进入FIFO队列中等待。
AQS是公平锁还是非公平锁
当新的线程与FIFO队列中的线程共同争抢资源的时候,是非公平锁
当新的线程到队列中等待,只让FIFO的head线程获取锁,是公平锁
它有一个实现类是ReentrantLock 默认是非公平锁
10.ReentrantLock 可重入锁
相对于Synchronized它具备一下特点
1.可中断
2.可以设置超时时间
3.可以设置公平锁
4.支持多个条件变量
实现原理:
它主要利用CAS+AQS队列来实现的。支持公平锁和非公平锁。
在构造方法中有一个可以选择的参数 默认是非公平锁 , 当设置成true 的时候 就表示成公平锁 。 公平锁的效率相比于非公平锁是比较低的。所以在多线程的访问下,公平锁的吞吐量会比较的低。
使用步骤:
因为它是一种手动锁,需要手动获取锁和释放锁,它更加灵活,但是也要注意释放锁,避免一个死锁的情况发生,所以通常需要配合try/finally语句来实现。 在finally中释放锁,保证即使出现了异常也能正常释放锁。
11.Volatile 关键字
volatile 是一个关键字,可以修饰类的成员变量、类的静态成员变量,主要 有两个功能
第一:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。
第二: 禁止进行指令重排序,可以保证代码执行有序性。底层实现原理是,添加了一个内存屏障,通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化
12.线程池的创建方式
JDK默认提供了四种方式创建,具体的名字我记不太清楚了,因为这四种方式都有一定的缺点,并且在我们公司的项目中也是禁止使用的,项目中我们通常使用自定义的线程池来进行创建。
13.线程池的核心参数
核心参数总共有七个,分别是
核心线程数
最大线程数
非核心线程数存活时间
存活时间单位
最大等待队列数量
线程工厂
拒绝策略(四种)
1.抛异常(默认)
2.由调用者执行任务
3.丢弃当前任务
4.丢弃最早排队的任务
14.如何确定核心线程数
是这样的,我们公司当时的规范是,为了减少线程上下文的切换,要根据当前部署的cpu的核心数量来确认
通常是cpu核心数量+1
15.线程池的执行原理
当线程数量大于等待队列的数量的时候 会创建非核心线程 来执行任务, 如果都满了 那么会执行拒绝策略