1、进程与线程的区别
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
进程A和进程B的内存独立不共享。
线程A和线程B,堆内存 和 方法区 内存共享。但是 栈内存 独立,一个线程一个栈。
2、为什么要有多线程?
目的是为了提高程序的处理效率
3、并行和并发有什么区别?
- 并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
- 并行:多个处理器或多核处理器同时处理多个任务。
示例:
并发 = 两个队列和一台咖啡机。
并行 = 两个队列和两台咖啡机。
4、守护线程是什么?
守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护线程的设置方式:t1.setDaemon(true);
在 Java 中垃圾回收线程就是特殊的守护线程。守护线程拥有自动结束自己生命周期的特性。
主线程执行结束后守护线程也结束,非守护线程不会结束,导致程序不会结束,所以垃圾回收线程是守护线程,不然程序不会结束
5、创建线程有哪几种方式?
创建线程有三种方式:
- 继承 Thread 重写 run 方法;
- 实现 Runnable 接口;无返回值
- 实现 Callable 接口;有返回值
- 线程池创建;
6、sleep() 和 wait() 有什么区别?
类的不同:sleep() 来自 Thread,wait() 来自 Object。
释放锁:sleep() 不释放锁;wait() 释放锁。
用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
7、notify()和 notifyAll()有什么区别?
- notifyAll()会唤醒所有的线程,会将全部线程由等
待池移到锁池,然后参与锁的竞争。 - notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
8、线程的 run() 和 start() 有什么区别?
- start() 方法用于启动线程,在jvm中开辟一个新的栈空间,运行run()方法,当run()运行结束后,栈空间销毁
- run() 方法用于执行线程的运行时代码
9、创建线程池有哪几种方式?
1、通过Executor 执行器创建线程
2、通过ThreadPoolExecutor() 手动创建线程
线程池创建有七种方式,最核心的是最后一种:
- newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
- newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
- newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目nThreads;
- newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
- newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
- newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建 ForkJoinPool,利用 Work-Stealing 算法,并行地处理任务,不保证处理顺序;
- ThreadPoolExecutor():是最原始的线程池创建,上面 1-3 创建方式都是对 ThreadPoolExecutor 的封装。
10、线程有哪些状态?
- NEW 新建状态 线程创建,开辟工作空间
- START 就绪状态 加入队列等待CPU分配时间片
- RUNNABLE 运行状态执行run() 方法
- BLOCKED 阻塞状态
- sleep()方法 不释放线程,可以设置睡眠时间(线程阻塞时间),调用Interrupt()方法手动唤醒
- wait() 方法 释放线程,可以设置阻塞时间,可以使用notify()或notifyAll(),启动线程,被启动的线程进入就绪状态
- join() 方法 加入、合并或者是插队,这个方法阻塞线程到另一个线程完成以后再继续执行
- IO 阻塞,比如 write() 或者 read() ,因为IO方法是通过操作系统调用的。
注:并不是调用就立马阻塞,而是看是否具有CPU时间片
- TERMINATED 死亡状态,run()方法执行结束,线程执行完成
执行顺序:NEW -------> START -------> RUNNABLE ------->BLOCKED -------> TERMINATED

11、线程池都有哪些状态?
- RUNNING 运行状态
- SHUTDOWN 关闭状态
- STOP:停止状态
- TIDYING:整理状态
- TERMINATED:销毁状态

12、Java线程池七个参数
- 核心线程数 corePoolSize
- 最大线程数 maximumPoolSize
- 空闲线程存活时间 keepAliveTime
- 时间单位 unit
- 工作队列 workQueue
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
- LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
- SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQuene;
- priorityBlockingQuene:具有优先级的无界阻塞队列;
- 线程工厂 threadFactory
- 拒绝策略 handler
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
- CallerRunsPolicy:由调用线程处理该任务。
13、线程池执行流程
- 在创建了线程池后,开始等待请求。
- 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
- 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
- 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
- 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
- 所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

14、线程池常用API
- getActiveCount() :获取线程池中正在执行任务的线程数量
- getCompletedTaskCount():获取已执行完毕的任务数
- getTaskCount() :获取线程池已执行与未执行的任务总数
- getPoolSize():获取线程池当前的线程数
- getQueue().size():获取队列中的任务数
15、在 Java 程序中怎么安全的中断一个正在运行的线程?
isInterrupted()方法与interrupt()配合
使用isInterrupted()方法在线程内部埋下一个钩子(判断线程是否调用interrupt())调用时就中断
16、在 Java 程序中怎么保证多线程的运行安全?
方法:要保证线程安全,就必须保证线程同步。保证线程的可见性,有序性和原子性。
思路:保证线程可见性、禁止指令重排序、保证线程同步
- 使用安全类,比如 Java. util. concurrent 下的类。
- 使用自动锁 synchronized。
- 使用手动锁 Lock。
17、多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:
- 在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id
- 再次进入的时候会先判断threadid 是否与其线程 id 一致,
- 如果一致则可以直接使用此对象
- 如果不一致,则升级偏向锁为轻量级锁
- 通过自旋循环一定次数来获取锁,执行一定次数之后
- 如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁
此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

739

被折叠的 条评论
为什么被折叠?



