1、什么是线程?进程?两者区别?
- 线程:是操作系统能够进⾏运算调度的最⼩单位,由进程创建的,是进程的一个实体,线程也可以创建线程;
- 进程:正在运行的一个程序,一个进程可以拥有多个线程;
- 区别:
- 它们是不同的操作系统资源管理方式,线程只是一个进程中的不同执行路径,创建线程开销⼩,线程属于进程,不能独⽴执⾏;
- 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,但在进程切换时,耗费资源较大,效率要差一些;
- 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
2、CPU和多线程的关系?
- Central Progressing Unit 中央处理器,是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。
- 单CPU时代,单CPU在同一时间点,只能执行单一线程;
- 单CPU多任务阶段,计算机在同一时间点,并行执行多个线程;
- 多CPU多任务阶段,真正实现的,在同一时间点运行多个线程;
- CPU的线程数概念仅仅只针对Intel的CPU才有用,因为它是通过Intel超线程技术来实现的,对于AMD的CPU来说,只有核心数的概念,没有线程数的概念。
3、什么是线程安全?线程不安全?如何避免?
-
线程安全:多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏访问,直到该线程读取完,其他线程才可使⽤。不会出现数据不⼀致或者数据污染。
-
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
-
线程安全问题都是由全局变量及静态变量引起的。
-
线程安全问题的解决思路:
- 尽量不使⽤共享变量,将不必要的共享变量变成局部变量来使⽤;
- 使⽤Synchronized关键字同步代码块,或者使⽤jdk包中提供的接口类Lock锁为操作进⾏加锁;
- 使⽤ThreadLocal为每⼀个线程建⽴⼀个变量的副本,各个线程间独⽴操作,互不影响。
4、线程的生命周期?
线程的生命周期包含5个阶段,包括
- 新建:当一个线程被创建时,此时线程处于新建状态;
- 就绪:当调用start()方法时,就会进入线程队列等待CPU时间片,此时它已经具备了运行的条件,只是没有被CPU分配到执行权力;
- 运行:当就绪的线程被CPU时间片调度并获得CPU资源时,便进入运行状态,开始运行run()方法中的线程操作;
- 阻塞:当被认为挂起,或执行输出输入操作时,会让出CPU并临时中止自己的执行,进入阻塞状态;
- 销毁:线程完成了全部工作后或者被强制性的终止,或者出现异常,线程就会死亡。
5、wait、sleep、join 、yield的区别
- sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器;
- join⽤于让当前执⾏线程等待join线程执⾏结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait;
- yield方法可以暂停当前正在执行的线程对象,让其它相同优先级的线程执行。它是一个静态方法,只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield的线程有可能在进入到暂停状态后马上又被执行。
6、Synchronized和Lock的区别
- lock是一个接口,而synchronized是java的一个关键字;
- synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。一般会在finally块中写unlock()以防死锁;
- 如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized;
- Lock 有比 synchronized 更精确的线程语义和更好的性能,Lock可以提高多个线程进行读操作的效率。
7、ThreadLocal、Volatile、Synchronized 的作用和区别
- ThreadLocal:不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性;
- volatile:主要是用来在多线程中同步变量,volatile一般用于声明简单类型变量,使得这些变量具有原子性;
- synchronized:关键字保证了数据读写一致和可见性等问题。但是他是一种阻塞的线程控制方法,在关键字使用期间,所有其他线程不能使用此变量。
8、同步方法和同步块,哪个更好?
- 同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率;
- 同步的范围越小越好。
9、什么是死锁?如何避免死锁?
- 死锁就是两个线程相互等待对方释放对象锁。
- 如何避免死锁:
- 避免⼀个线程同时获取多个锁
- 避免⼀个线程在锁内同时占⽤多个资源,尽量保证每个锁只占⽤⼀个资源
- 尝试使⽤定时锁,使⽤ lock.tryLock(timeout) 来代替使⽤内部锁机制
- 对于数据库锁,加锁和解锁必须在⼀个数据库连接⾥,否则会出现解锁失败的情况
10、常用的线程池?作用?参数有哪些?
- 常见线程
- newCachedThreadPool可缓存线程池,如果线程池⻓度超过处理需要,可灵活回收空闲线程,若⽆可回收,则新建线程。
- newFixedThreadPool 定⻓线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 定时线程池,⽀持定时及周期性任务执⾏。
- newSingleThreadExecutor 单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有 任务 按照指定顺序(FIFO, LIFO, 优先级)执⾏。
- 作用
- 减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务
- 可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为消耗过多的内存使服务器崩 溃,减少在创建和销毁线程上所花的时间以及系统资源的开销
- 参数
- corePoolSize:核心线程数量
- maximumPoolSize:最大线程数量
- keepAliveTime:非核心线程闲置时的存活时间
- BlockingQueue:阻塞队列,存储等待执行的任务
- ThreadFactory:线程工厂,用来创建线程
- RejectedExecutionHandler:队列已满,而且任务量大于最大线程数量的异常处理策略
11、线程创建的几种方式
- 继承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是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口;
- 使用FutureTask对象作为Thread对象的target创建并启动新线程;
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
- 使用线程池创建
- 使用lambda表达式