1,什么是多线程
多线程就是在同一时间干多件事
- 并行:多个流水线同时进行(一个流水线一个核心(线长))
- 并发:一个核心(线长)管理多个线程,(流水线)进行快速交替工作
快速交替:线长去一号线干活五分钟,然后去2号线干6分钟,再去3号线干四分钟
2,多线程的好处
1- 例如:空调制作
非多线程:先制造空调本体,再制造空调遥控器,按顺序执行
多线程:在制造空调本体的同时,再开一条流水线做空调遥控器,两条流水线一起执行
2- 线程可以认为是一个轻量级的进程,线程的创建,销毁性能开销更小
3,多线程的弊端
如果并发的线程数量很多,并且每个线程执行的任务很短,这样就会频繁的创造和销毁线程,会大大降低系统的效率,如果大量线程在执行,会涉及到线程上下文的切换,会极大消耗cpu运算资源
4,如何创建线程
- 继承Thread
- 实现Runnable接口
- 使用 Callable接口(可以使用CompletableFutrue)
5,线程安全
多线程在对共享数据操作的时候,可能会导致数据错乱,这就是线程安全
如何判断当前程序是否存在线程安全
- 是否存在多线程环境
- 在多线程环境下是否存在共享变量
- 在多线程环境下是否存在对共享变量 “ 写 ” 操作
6,线程的生命周期
- 新建状态(New):新创建了一个线程对象
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权限
- 运行状态(Running):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
- 死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期
7,tait和sleep的区别
共同点:
- wait(),wait(long)和sleep(long)的效果都是让当前线程放弃CPU的使用权,进入阻塞状态
不同点:
- 方法归属不同
- sleep(long)是 Thread 的静态方法
- wait(),wait(long)都是 Object 的成员方法,每个对象都有
- 醒来时机不同
- 执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
- wait(long)和wait()还可以被notify唤醒,wait()如果不换形就一直等待下去
- 它们都可以被打断唤醒
- 锁特性不同
- wait 方法的调用必须先获取wait对象的锁,而sleep则无此限制
- wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃cpu,但你们还可以用)
- 而sleep 如果在synchronized代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)
8,valatile的作用和原理
JMM内存模型
JMM让java程序与硬件指令进行了隔离
- 由于JVM运行程序的实体是线程,创建每个线程时,java内存模型会为其创建一个工作内存(一般称为栈),工作内存是每个线程的私有数量区域
- java内存模型规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问
- 但线程对变量的操作(读取,赋值等)必须在工作内存中进行。因此首先要将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量写回主内存中
9,Java并发编程要解决的三个问题(三大特征)
原子性
一个线程在cpu中操作不可指定,也不可中断,要么执行完成,要么不执行
内存可见性
默认情况下,当一个线程修改内存中某个变量时,主内存值发生了变化,并不会主动通知其他线程,即其他线程并不可见
有序性
程序执行的顺序按照代码的先后顺序执行
10,为什么用线程池 线程池参数
- 降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗
- 提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行
- 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控
/*
corePoolSize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会
消除,而是一种常驻线程
maxinumPoolSize 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程
数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但
是线程池内线程总数不会超过最大线程数
keepAliveTime 、 unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会
消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过keepAliveTime 、
unit 表示超出核心线程数之外的线程的空闲存活时间,
也就是核心线程不会 setKeepAliveTime 来设置空闲时间
workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放
入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建
工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择
自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
Handler 任务拒绝策略,有两种情况,第一种是当我们调用 shutdown 等方法关闭线程池后,这
时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程
池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提
交的任务时,这是也就拒绝
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
java中常见的几种线程池
// 创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程
//若无可回收,则新建线程
Executors.newCachedThreadPool();
//创建一个定长线程池,可控制线程最大并发数,超出的线程会再队列中等待
Executors.newFixedThreadPool();
//创建一个定长线程池,支持定时及周期性任务执行
Executors.newScheduledThreadPool(10);//核心线程数
//创建一个单线程化的线程池,它只会唯一的工作线程来执行任务
//保证所有任务按照指定顺序(FIFO , LIFO , 优先级)执行
Executors.newSingleThreadExecutor();
11,synchronized
synchronized 锁释放时机
- 当前线程的同步方法,代码块执行结束的时候释放
- 正常结束
- 异常结束出现未处理的error 或者 exception 导致异常结束的时候释放
- 程序执行了同步对象wait方法,当前线程暂停,释放锁(sleep不会释放锁)
12,synchronized 和 ReentrantLock的区别
- synchronized 是一个关键字,ReetrantLock是一个类
- synchronized的底层是JVM层面的锁(底层由C++编写实现),ReentranLock是API层面的锁(java内部的一个类对象)
- synchronized会自动的加锁与释放锁,ReentrantLock需要程序员手动加锁与释放锁
- synchronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁
注: 假设多个线程都要获取锁对象,满足先等待的线程先获得锁则是公平锁,否则是非公平锁
- synchronized锁的是对象,锁信息保存在对象头中,ReebntrantLock通过代码中int类型的starte表示来标识锁的状态
- sychronized底层有⼀个锁升级的过程(访问对象线程数由少到多,竞争由不激烈到激烈,底层会通过一种锁升级机制 无锁->偏向锁->轻量级锁->重量级锁,保证性能) ,会使用自旋 线程频繁等待唤醒会浪费性能,特别是锁的获取也许只需要很短的时间 ,不限于等待,直接执行简单代码while(true)执行完抢锁 来优化性能