4.java多线程
1. 线程池的作用
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
- 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 第三:提高线程的可管理性。
2.创建线程池的方式有哪些?
-
newCachedThreadPool:
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 -
newFixedThreadPool:
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中 -
newSingleThreadExecutor:
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 -
newScheduleThreadPool:
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
3.线程池的几个重要参数有哪些?如何合理配置线程池的大小?
corePoolSize : 核心线程数量
maximumPoolSize : 线程最大线程数
workQueue : 阻塞队列,存储等待执行的任务 很重要 会对线程池运行产生重大影响
keepAliveTime : 线程没有任务时最多保持多久时间终止
*unit : *keepAliveTime的时间单位
threadFactory : 线程工厂,用来创建线程
rejectHandler : 当拒绝处理任务时的策略
线程池大小的配置:
-
CPU密集型 尽量使用较小的线程池,一般Cpu核心数+1 因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销
-
IO密集型 方法一:可以使用较大的线程池,一般CPU核心数 * 2 IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别…
-
混合型 可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定
4.并行和并发有什么区别?
- 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
5.创建线程有哪几种方式?
- 继承Thread类
创建线程类定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。创建Thread子类的实例,即创建了线程对象。调用线程对象的start()方法来启动该线程。
- 通过Runnable接口创建线程类
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。调用线程对象的start()方法来启动该线程。
- 通过Callable和Future创建线程
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。使用FutureTask对象作为Thread对象的target创建并启动新线程。调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
6.sleep() 和 wait() 有什么区别?
-
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
-
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。
-
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
7.线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容,
Run方法运行结束, 此线程终止。然后CPU再调度其它线程。run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
8.线程池都有哪些状态?
线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。线程池各个状态切
换框架图:
9.实现同步的三种方法
- 同步代码块 :
格式:
synchronized(锁对象){
可能会出现线程安全的代码(访问了共享数据的代码)}
通过代码块中的锁对象,可以使用任意的对象
但是必须保证多个线程使用的锁对象是同一个
把要同步访问的代码块锁住,只让一个线程在synchronized的代码块中执行,同步中的线程没有执行完毕不会释放锁,其他线程阻塞。
- 同步方法
使用synchronized修饰的方法,保证线程执行该 方法的时候,其他线程只能在外面等待。run()中调用同步方法。
Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。锁的是this 即是这个对象
public synchronized void method(){
可能会产生线程安全的代码
}
- 静态同步方法
Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。
因为对静态对象加锁实际上对类(.class)加锁,类对象只有一个,可以理解为任何时候都只有一个空间,里面有N个房间,一把锁,因此房间(同步方法)之间一定是互斥的。
public synchronized static void staticMethod1(){
可能产生问题的代码块
}
10.什么是用户线程和内核线程
用户线程(ULT):
用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/内核态的转换。 内核对于ULT无感知,线程阻塞则进程(包括它所有的线程)阻塞。
内核线程(KLT):
由系统内核管理的线程,内核保存线程的状态和上下文信息,线程阻塞不会引起进程的阻塞。再多处理器系统上,多线程在多处理器上并行运行。线程的创建、调度、和管理由内核完成,效率 要比ULT慢,比进程操作快
6.阻塞队列
- 在任意时刻,不管并发有多高,永远只有一个行程能够就你行队列的入队或者出队操作!也就是说是线程安全的。
- 它是一个有界队列。当队列满时只能进行出队操作,所有入队的操作必须等待,也就是被阻塞;当队列空时只能进行入队操作,所有的出队操作必须等待,也就是被阻塞。