并发基础
为什么要使用并发编程
并发编程可以提高性能
什么是上下文切换
多线程编程中,当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以在加载这个任务的状态。
(任务从保存到在加载的过程就是一次上下文切换)
并发编程有什么缺点
线程安全,死锁等问题
串行,并行,并发的区别
串行:多个任务 由一个线程按顺序执行
并行:多个任务 由多个处理器同时执行
并发:多个任务 在同一个CPU核交替执行
Java线程
进程和线程的区别是什么?
进程是处于运行状态的应用程序,
而线程是进程里面的一个执行序列
(一个进程可以有多个线程)
线程有哪些状态? 生命周期?
新建, 运行, 可运行, 阻塞, 死亡
(不同线程要抢CPU的使用权, 谁抢到了, 谁就处于运行状态)
怎么启动线程?
调用start()方法启动线程
如何停止一个线程
run方法执行完后,线程会自动终止
调用interrupt()方法中断线程
调用stop() 强行终止线程
多线程的实现方式 / 创建线程有几种不同的方式?
3种方式
写一个类
继承Thread类,重写run()方法
实现Runnable接口,重写run()方法
实现Callable接口,重写call()方法
然后调用start()方法启动线程
runnable和callable有什么区别
相同点:
都是接口;
都可以编写多线程程序
都采用 Thread.start()启动线程
区别:
Runnable接口的run方法没有返回值,无法抛出返回结果的异常
Callable接口的call方法有返回值,Callable接口支持返回执行结果,需要调用FutureTask.get()得到
什么是守护线程
守护线程在后台运行,周期性的执行某种任务,最典型的就是java中的垃圾回收线程
线程的run()和start()有什么区别
start()方法用于启动线程,run()方法用于执行线程的任务
run()方法只是线程的一个函数,调用run()方法不能说明是多线程,调用了start()方法才表示实现了多线程运行
线程类的构造方法,静态块是被哪个线程调用的
线程的run方法都是线程自己调用的
线程类的构造方法、静态块,是new实例所在的线程调用的
与线程相关的方法
wait():使线程处于等待/阻塞状态,并且释放所持有的对象锁
sleep():使线程处于睡眠状态,不会 释放对象锁,时间到了继续执行下面的代码
notify():唤醒一个处于等待状态的线程,由JVM确定唤醒哪个线程
notifyAll():唤醒所有处于等待状态的线程,只有获得锁的线程才能进入可运行状态
(线程被唤醒后,并不能马上执行,而是要获取该线程相应的对象锁才能运行)
sleep()和wailt()有什么区别
sleep()来自Thread,wait()来自Object
sleep()不释放锁,时间到会自动恢复
wait()会释放锁,可以使用notify()/notifyAll()唤醒
sleep()通常用于暂停执行
wait()通常用于线程间交互/通信
(sleep是占着CPU 却不用, wait是等待CPU的使用权)
notify()和notifyAll()有什么区别
notifyAll()会唤醒所有的线程,notify()只会唤醒一个线程
notifyAll()调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。
而notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制
为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类?
Java中,任何对象都可以作为锁,而任何对象都可以调用的方法一般定义在Object类中
怎么调用wait()方法?使用if块还是循环?为什么
wait()方法应该在循环中调用,因为当线程获取到锁时,其他条件可能还没满足,
所以使用循环检测条件是否满足会更好
Java线程数量过多会造成什么异常
消耗过多的CPU,给垃圾回收器带来压力
降低JVM的稳定性,可能抛出OOM异常 (OutOfMemoryError)
Java线程池
什么是线程池
线程池是用于管理线程的一个工具
就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销
线程池有什么优点
降低资源消耗
提高响应速度
提高线程的可管理性
(使用线程池可以进行统一的分配,调优和监控)
怎么创建一个线程池
可以使用Executors工具类创建线程池
也可以使用ThreadPoolExcutor创建自定义线程池 (阿里规范)
Executor和Executors的区别
Executor接口对象能执行我们的线程任务,
Executors工具类可以创建线程池
Executors和ThreadPoolExecutor创建线程池的区别
Executors的方法可能会耗费非常大的内存,甚至是OOM
ThreadPoolExecutor创建线程池的方式只有一种,就是走它的构造函数,参数自己指定
ThreadPoolExecutor构造函数的参数有哪些
线程池有哪些状态
线程池中submit()和execute()方法有什么区别
execute():只能之行Runnable类型的任务
submit():可以执行Runnable和Callable类型的任务
Callable类型的任务可以获取执行的返回值,而Runnable执行无返回值
如果你提交任务时,线程池队列已满,这时会发生什么
如果使用的是的是无界队列LinkedBlockingQueue,没关系,继续添加任务到阻塞队列中等待执行即可
如果使用的是有界队列 比如ArrayBlockingQueue,任务首先会被添加到有界队列中,有界队列满了,会根据maxmumPoolSlize的值增加线程数量,如果增加了线程数量还是处理不过来,就会拒绝新任务的处理
线程安全
什么是线程安全
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
实现线程安全有几种方式?
3种
方法一:使用安全类,比如Java.util.concurrent下的类
方法二:使用自动锁synchronized (同步方法,同步代码块)
方法三:使用手动锁Lock
说明:加锁会让线程安全, 但其他请求都要等拿到锁的人释放锁才能访问, 会比较慢, 用户体验不太好
synchronized的作用?
synchronized关键字是用来控制线程同步的,就是在多线程环境下,控制synchronized代码段不被多个线程同时执行
一旦方法或者代码块被synchronized修饰,那么这部分就放入了监视器Monitor的监视范围,一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码
synchronized和volatile的区别
volatile只能修饰变量
synchronized可以修饰类,方法,代码段
volatile可以实现共享变量的可见性和禁止指令重排序
synchronized可以实现变量的可见性和原子性
synchronized是悲观锁,属于抢占式,会引起其他线程阻塞
volatile修饰的变量不会在多个线程中存在复本,直接从内存读取
synchronized和Lock的区别
synchronized是Java的一个关键字,Lock是一个接口;
synchronized会自动释放锁,而Lock要手工释放,并且必须在finally代码块中释放
同步方法和同步代码块的区别是什么?
他们的锁对象 不一样
同步块和同步方法,那个是更好的选择
同步的范围越小越好,同步范围越大 其他线程等待的时间越长,越容易发生死锁
同步块只在需要锁住的代码块锁住相应的对象
同步方法会锁住整个方法
所以说同步块是更好的选择
线程B怎么知道线程A修改了变量
用volatile修饰变量
或者用synchronized修饰修改变量的方法
当一个线程进入一个对象的一个synchronized方法后,其他线程是否可进入此对象的其他方法
如果其他方法没有加synchronized关键字 就可以
并发锁
悲观锁和乐观锁的区别
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
多线程的synchronized
数据库中的行锁,表锁等都是悲观锁,
都是在操作之前先上锁,
乐观锁:
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制
乐观锁的实现方式
使用版本标识来确定读到的数据与提交时的数据是否一致。提交后 修改版本标识,不一致时可以采取丢弃和再次尝试的策略
公平锁与非公平锁:
公平锁,先对锁发出获取请求的一定先获得锁。非公平锁则反之(性能更高)。
什么是死锁
死锁会让程序一直卡住,无法向下执行
我们只能通过中止并重启的方式来让程序重新执行
造成死锁的原因
当前线程拥有其他线程需要的资源
当前线程等待其他线程已拥有的资源
都不放弃自己拥有的资源
从而陷入无限等待
避免死锁的方法
1.所有线程以固定的顺序来获得锁
2.缩小加锁的范围,仅操作共享变量时才加锁,尽量不要几个功能用同一把锁
3.使用tryLock()定时锁,设置超时时间,超时可以退出防止死锁