线程与进程的区别(面试常考)
- 本质
- 进程是内存中运行的应用程序
- 线程是进程的执行路径,每个进程必须至少一个线程
- 内存空间
- 进程拥有独立内存空间
- 线程共享进程的内存空间,线程私有程序计数器,虚拟机栈,本地方法栈
- 切换
- 进程切换要保存、还原上下文,比较慢
- 线程切换较快
线程调度
有以下几种方法
- 分时调度
- 所有线程轮流获得CPU使用权,平均分配每个线程占用CPU的时间(时间片)
- 抢占式调度
- 优先级高的线程使用CPU,如果优先级相同就随机,java使用的也是抢占式调度。
- CPU使用抢占式调度,在多线程间高速切换,看上去就是同一时刻运行多个。能让CPU使用率更高。
多线程并不能提高程序运行速度,但能提高运行效率
同步与异步
- 同步 排队执行 效率低但安全
- 异步 同时执行 效率高但不安全
并发与并行(面试常考)
- 并发:多个时间同一时间段发生,不管是同时发生还是排队执行
- 并行:确实在同一时刻发生,同时执行。
多线程实现方法(面试常考)
如何创建多线程?
- 继承Thread类,覆写run()方法,创建Thread对象,通过Thread对象的start()来启动
- 实现Runnable接口,覆写run()方法,但还是要创建Thread对象,把Runnable对象传给Thread对象(为线程分配任务)。所以跟1差不多。(比1有好处)
1.继承Thread覆写run方法,而且需要用Tread对象的start()来启动任务,在线程里调用run方法
public static void main(String[] args){
MyThread my = new MyThread();
my.start();
for(int i=0;i<5;i++){
System.out.println("b"+i);
}
}
public class MyThread extends Thread{
@Override
public void run(){
//新的执行路径-线程,触发方式不是直接调用run,而是通过thread对象的start()方法来调用
for(int i=0;i<5;i++){
System.out.println("a"+i);
}
}
}
a和b并发执行,每次输出结果都不一样。
执行流程:
- main线程开启->main方法执行
- 开启子线程m(main方法执行时)
内存区域分配:
- 开启子线程m(main方法执行时)
- 每个线程拥有自己的栈空间,共用一份堆内存
2.实现Runnable接口,也是覆写run
public class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println("a"+i);
}
}
}
public static void main(String[] args){
//1. 创建任务对象Runnable
MyRunnable r = new MyRunnable();
//2. 创建线程thread并为其分配任务r
Thread t = new Thread(r);
//3. 执行线程
t.start();
for(int i=0;i<5;i++){
System.out.println("b"+i);
}
}
与继承Thread相比有优势:
- 可以多实现,比单继承更灵活
- 创建任务和线程分配来实现,更适合多个线程同时执行相同任务的情况
- 任务和线程是分离的,提升健壮性
- 线程池技术,只接收Runnable类型的任务而不接收Thread类型的线程
通过匿名内部类开启线程
new Thread(){
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println("a"+i);
}
}
}.start();
守护线程
守护用户线程的线程。
- 用户线程:一个进程不包含任何存活用户线程,进程结束
- 守护线程:当最后一个用户线程结束时,守护线程自动死亡
设置t1为守护线程,main方法结束就结束了
t1.setDaemon(true);
线程名称
new Thread(new MyRunnable(),"name").start();
设置线程名称.
Thread.currentThread().getName());
获取当前执行线程的名称
线程的6种状态(面试常考)
ENUM Thread.State
- NEW 尚未启动的线程
- Runnable 正在执行的线程
- Blocked 被阻塞 等待监视器锁定
- Waiting 无限期等待另一个线程执行特定操作 休眠
- TimedWaiting 等待另一个线程执行最多指定等待时间的操作?
- Terminated 已退出的线程
线程休眠
Thread.sleep(millis:1000);
线程阻塞
比如要读用户输入,线程会等着,耗时操作,等待用户输入完成。
线程中断(打标记,记得释放资源)
线程是一个独立的执行路径,他的生命周期因其自身决定。否则外部中断可能导致资源没有释放,一直占用。
线程可以打中断标记,它自己会看,如果有就会触发一个异常InterruptedException
,try catch来由程序员来决定是否关闭
t1.interrupt();
给线程t1添加中断标记。
添加标记之后进入catch块,发现中断标记:
- wait
- sleep
- interrupt
- interrupted
可以在异常处理里进行资源释放(交代后事)然后return,就可以让线程自己死亡。
线程安全问题(面试常考)
多个线程同时运行时会发生线程不安全的问题。
解决方法:
- 同步代码块 synchronized(锁对象){} 任何对象都可以作为锁存在
- 同步方法(1.2.都是隐式锁)
- 显式锁Lock
同步代码块(排队执行)
while(true){
synchronized (o) {
if (count > 0) {
System.out.println("卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "剩余:" + count);
} else {
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
同步方法
让卖票成功返回true,否则返回false。
public synchronized boolean sale(){
if(count>0){
//卖票
//trycatch sleep1000ms
count--;
}else break;
}
显式锁,自己创建锁
private Lock l = new ReentrantLock();//显式锁
@Override
public void run(){
while(true){
l.lock();
if(count>0){
//卖票
//trycatch让线程sleep1000ms
count--;
}else break;
l.unlock();//对count改变之后释放锁对象
公平锁和非公平锁
先来先得。
线程死锁
互相等待对方释放资源,占用自己所持资源。
多线程通信
api:
- notify()唤醒单个线程 notifyall全唤醒
生产者与消费者模型
确保生产者在生产时消费者没有在消费。
- 生产者生产后,flag=false,先唤醒消费者notifyAll(),再等待wait()
- 消费者消费后,flag=true,唤醒生产者,等待
- 用flag控制是否能生产。消费完了才能生产。
- 生产和消费的方法都需要用synchronized同步
- wait需要try catch
创建线程的方法->带返回值的线程Callable
功能接口,可以用作lambda表达式或方法引用的赋值目标。主线程可以拿到一个结果。
使用:编写实现Callable接口,实现call方法。
public static void main(String[] args){
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
task.get();//要抛出异常
//然后执行主线程剩下的代码
}
class MyCallable implements Callable<T>{
@Override
public Integer call() throws Exception{
Thread.sleep(3000);
return 100;
}
}
- task.isDone();//判断子线程是否执行完毕
线程池(池化技术)
线程与任务执行流程:
- 创建线程
- 创建任务
- 执行任务
- 关闭线程
如果创建和关闭线程的时间比真正创建和执行任务的时间多得多,会浪费大量时间。频繁创建会降低系统效率。线程池能存储大量线程。
线程池里的空闲线程可以用来执行任务,变为忙状态。
非定长,缓存线程池
newCachedThreadPool();
- 判断是否存在空闲线程
- 存在则使用
- 不存在 创建并放入线程池,然后使用
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行任务
service.execute(new Runnable(){
@Override...}
定长线程池
newFixedThreadPool(len);
- 前同
- 不存在
- 线程池已满 等待
- 不满,创建
单线程线程池 长度为1的定长线程池
newSingleThreadExecutor()
- 空闲则使用,不空闲则等待
周期性任务定长线程池 在某个时机触发时执行
- 是否空闲,存在则使用
- 不存在
- 未满 创建 放入 使用
- 已满 等待
- 周期性任务执行时,定时,自动执行
ScheduledExecutorService service = Executors.newScheduledThreadPool(size);
//任务 时长 时长的单位(TimeUnit的常量)
service.schedule(new Runnable(){
...
}
}, delay:5, period:1, TimeUnit.SECONDS);//5s后执行
Lambda表达式
1.8版本引入,属于函数式编程思想,让实现接口的写法更简单
Thread t = new Thread(()->System.out.println(""));
()内就是要传入的参数,(参数)->(方法体);