Java高频面试题复盘系列之线程
线程的创建?
线程创建的方式
1 继承Thread类, 重写run()方法
2 实现Runnable接口, 重写run()方法
3 实现Callable接口, 重写call方法
4 使用线程池
开启?
启动线程, 对应上面四种方式
1 创建自定义类的对象, 使用start()方法
1 new 自定义类().start()
2 创建Thread类对象, 把Runnable接口的实现类作为参数传递过去
1 new Thread(new Runnable).start()
3 new Thread(new FutureTask<E>(new Callable())).start();
状态?
线程状态
1 NEW: 新建状态
2 Runnable: 可运行状态
3 Blocked: 锁阻塞状态
4 Waitting: 无限等待状态
5 Timed waiting: 计时等待状态
6 Terminated: 被终止状态
状态
创建, 就绪, 运行, 阻塞, 死亡
sleep和wait的区别, 线程池?
如何停止一个线程?
1 没有直接停止线程的方法, 只能等线程运行完毕
2 如果要手动停止, 你可以使用volatile布尔变量来退出run()方法的循环或者是取消任务来中断线程
sleep和wait的区别
1 sleep: 让当前线程睡眠, 在同步方法或代码块中不释放锁
1 该方法可以写在任何位置
2 wait: 让当前线程等待, 同时释放已经拿到的锁
1 该方法必须写在同步方法或同步块中
线程池
1 线程池的组成
1 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
2 一个服务器完成一项任务所需时间为: T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
1 如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
3 线程池类ThreadPool
1 默认初始化5个工作线程
2 我们可以通过execute(List<Runnable> task)方法将自己的任务传递进去
1 或使用其他重载的方法
execute(Runnable task)
execute(Runnable[] task)
1 自己的任务实现Runnable接口, 重写其中的run()方法
2 但只是把自己的任务加入了线程池, 什么时候执行由线程池决定
4 线程池有几种
1 Executors.newFixedThreadPool, 创建固定线程数的线程池,使用的是LinkedBlockingQueue无界队列,线程池中实际线程数永远不会变化
2 Executors.newSingleThreadExecutor (单线程线程池),创建只有一个线程的线程池,使用的是LinkedBlockingQueue无界队列,线程池中实际线程数只有一个
3 Executors.newCachedThreadPool (弹性缓存线程池),创建可供缓存的线程池,该线程池中的线程空闲时间超过60s会自动销毁,使用的是SynchronousQueue特殊无界队列
4 Executors.newScheduledThreadPool (定时器线程池),创建可供调度使用的线程池(可延时启动,定时启动),使用的是DelayWorkQueue无界延时队列
5 Executors.newWorkStealingPool 拥有多个任务队列的线程池,jdk1.8提供的线程池,底层使用的是ForkJoinPool实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu核数的线程来并行执行任务
6 ForkJoinPool
5 线程池参数
corePoolSize,线程池中的常驻核心线程数
int maximumPoolSize,线程池能够容纳同时执行的最大线程数,此值必须大于等于1
long keepAliveTime,多余的空闲线程的存活时间。
TimeUnit unit,keepAliveTime的单位
BlockingQueue<Runnable> workQueue,任务队列,被提交但尚未被执行的任务
ThreadFactory threadFactory,表示生成线程池中工作线程的线程工厂,用于创建线程一般默认即可
RejectedExecutionHandler handler拒绝策略,表示当队列满了并且工作线程大于等于线程池最大线程数(maximumPoolSize)时如何处理
死锁?
死锁
1 死锁介绍: 死锁就是线程1需要请求的资源被线程2持有, 而线程2想要获取的资源被线程1持有
1 两个线程都不释放自己的资源, 但又无法获取所需的资源而进入阻塞的一种状态
2 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者进程在运行过程中,请求和释放资源的顺序不当而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
2 死锁产生的条件
1 有多把锁
2 有多个线程
3 有同步代码块嵌套, 即一个线程完成一个任务可能需要拿到多把锁
3 死锁解决方法
1 杀死一个或多个进程(最简单的方法)
2 抢占: 即从一个线程中获取资源, 将其分配给其他线程, 但可能会导致一些问题
3 回滚: 系统可以定期记录每个进程的状态, 在产生死锁时, 将所有内容回滚到最后一个检查点
1 然后重新启动, 但以不同的方式分配资源, 以免发生死锁
4 按照顺序加锁
1 即如果有两把锁, 必须先获取第一把锁之后才能获取第二把锁
2 不能直接先获取第二把锁
5 不要用synchronized无限等待, 可以使用ReentranLock实现超时等待
1 即拿到第一把锁之后,在一定时间后拿不到第二把锁,就释放已经拿到的锁
4 活锁: 死锁的变体
1 例子: 两个人在走廊上面对面相遇, 每个人一边走一边让对方通过,但最终却在没有任何进展的情况下左右摇摆, 因为他们总是以相同的方式移动同时
5 只有一个锁, 如果这个锁是互斥锁时, 也可能会产生死锁
如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁;
然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,。因此就永远处于挂起等待状态了
如何保证线程安全?
如何保证线程安全?
1 线程安全在三个方面体现
1 原子性: 供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized)
2 可见性: 一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile)
3 有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序
2 解决方法
1 使用原子类
1 在Java.util.concurrent.atomic包下定义了一些对"变量"操作的"原子类"
2 原子类又称为乐观锁, cas机制 compare and swap
2 使用 synchronized 关键字
3 使用 Lock 锁
lock()获得锁
unlock()释放锁