Java多线程总结

1、线程和线程池分别都有哪些状态?

线程的状态:

new 尚未启动

runnable RUNNABLE 正在执行中

blocked 阻塞的(被同步锁或者IO锁阻塞)

waiting 永久等待状态

timed_waitng 等待指定的时间重新被唤醒的状态

terminated 执行完成

线程池的状态:

RUNNING:这是正常的状态,接受新的任务,处理等待队列中的任务。

SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。

STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。

TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会 执行钩子方法 terminated()。

TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

 

2、线程池中 submit() 和 execute() 方法有什么区别?

execute():只能执行 Runnable 类型的任务。

submit():可以执行 Runnable 和 Callable 类型的任务。

Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返回值。

 

3、在 Java 程序中怎么保证多线程的运行安全?

使用安全类,比如 Java. util. concurrent 下的类。

使用自动锁 synchronized。

使用手动锁 Lock。

 

4、创建线程池有哪几种方式?

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的 封装。

 

5、4种常用线程池的使用方式和场景

newCachedThreadPool ():

缓存线程池;适用于执行大量(并发)短期异步的任务;注意,任务量的负载要轻;

比如同时给很多人发送从磁盘读取的消息通知。

newFixedThreadPool():

定长线程池;适用于执行负载重,cpu使用频率高的任务;这个主要是为了防止太多线程进行大量的线程频繁切换,得不偿失;

比如同时很多人进行商品秒杀。

newSingleThreadExecutor():

单线程线程池; 这个一般用来执行需要按照指定顺序的任务。

newScheduledThreadPool():

周期定长线程池; 一般是周期性的任务,不过这个可以使用其他的替代;

 

6、线程池是什么?什么情况下使用线程池?使用线程的好处是什么?

线程池:是一种多线程处理形式,处理线程时将任务添加到队列里,等创建好线程再执行队列里任务。线程池的线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。
什么情况下使用线程池?
1、单个任务处理时间比较短
2、处理任务数量大
使用线程的好处?
1、避免重复创建线程,减少在创建和销毁线程时所花时间,及系统的整体开销
2、避免系统创建大量线程而消耗系统资源
3、用户提交的数据能够及时得到处理,响应速度快
4、能够更好的监控和管理线程

 

 

7、sleep() 和 wait() 有什么区别

类的不同:sleep() 来自 Thread,wait() 来自 Object。

释放锁:sleep() 不释放锁;wait() 释放锁。

用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

 

8、synchronized 和 volatile 的区别是什么

volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。

volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改 可见性和原子性。

volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

 

9、synchronized 和 Lock 有什么区别ReentrantLock?

synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁; 而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

 

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

主要区别如下:

ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作; ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁; ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。 volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

 

10、死锁

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的 情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

怎么防止死锁?

尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、 ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。

尽量使用 Java. util. concurrent 并发类代替自己手写锁。

尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。

尽量减少同步的代码块。

 

11、多线程中 synchronized 锁升级的原理是什么?

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量 级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此 时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方 式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

 

12、说一下 synchronized 底层实现原理

synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单 元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态 的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对 此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁 (Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

 

13、死锁的四个必要条件是什么?如何避免?

死锁的四个必要条件  

  1.互斥条件:一个资源每次只能被一个进程使用。
  2.请求和保持条件:一个进程因为请求资源而阻塞时,对已获得的资源保持不放。
  3.不剥夺条件:进程已经获得的资源在没有使用完之前,不能强行剥夺。
  4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

 解决死锁的基本方法:

  1.预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件
  2.避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁
  3.检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉
  4.解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来,该方法与检测死锁配合使用

死锁相关参考:

  死锁及死锁的四个必要条件

  死锁的四个必要条件以及怎样处理

  死锁,死锁的四个必要条件以及处理策略

posted @ 2021-01-03 22:22  奇遇yms  阅读(98)  评论(0编辑  收藏  举报