Java并发编程(一)并发编程的挑战

蔚落 2021-09-26 20:46 549阅读 0赞

1.1 上下文切换

  1. 时间片指的是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停的切换线程执行,让我们感觉多个线程是同时执行的。
  2. CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

1.1.1 多线程一定快吗?

  1. 并发执行的速度可能比串行慢?这是因为线程有创建和上下文切换的开销。

1.1.2 如何减少上下文切换

减少上下文切换的方法有无锁并发编程、CAS算法、使用最小线程和使用协程。
(1)无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照HASH算法取模分段,不同的线程处理不同段的数据。
(2)CAS算法。Java的Atomic包使用CAS算法更新数据,而不需要加锁。
(3)使用最小线程。避免创建不需要的线程,比如任务很少,但是创建了很多的线程来处理,这样会造成大量的线程都处于等待状态。
(3)协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

1.2 死锁

所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待)
生活中的一个实例,2个人一起吃饭但是只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃)。某一个时候,一个拿了左筷子,一人拿了右筷子,2个人都同时占用一个资源,等待另一个资源,这个时候甲在等待乙吃完并释放它占有的筷子,同理,乙也在等待甲吃完并释放它占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。

1.2.1 死锁产生的条件

(1)互斥条件。进程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个进程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的进程用毕释放。
(2)请求和保持条件。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己以获得的资源保持不放。
(3)不可抢占条件。进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放。
(4)循环等待条件。在发生死锁时,必然存在一个进程—资源的循环链,即进程集合{P0,P1,P2,P3,…,Pn}中的P0正在等待P1占用的资源,P1正在等待P2占用的资源,… … ,Pn正在等待已被P0占用的资源。
死锁的示例:

  1. /** * 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒 * 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒 * td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定; * td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定; * td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。 */
  2. public class DeadLock implements Runnable {
  3. public int flag = 1;
  4. //静态对象是类的所有对象共享的
  5. private static Object o1 = new Object(), o2 = new Object();
  6. @Override
  7. public void run() {
  8. System.out.println("flag=" + flag);
  9. if (flag == 1) {
  10. synchronized (o1) {
  11. try {
  12. Thread.sleep(500);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. synchronized (o2) {
  17. System.out.println("1");
  18. }
  19. }
  20. }
  21. if (flag == 0) {
  22. synchronized (o2) {
  23. try {
  24. Thread.sleep(500);
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. synchronized (o1) {
  29. System.out.println("0");
  30. }
  31. }
  32. }
  33. }
  34. public static void main(String[] args) {
  35. DeadLock td1 = new DeadLock();
  36. DeadLock td2 = new DeadLock();
  37. td1.flag = 1;
  38. td2.flag = 0;
  39. //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
  40. //td2的run()可能在td1的run()之前运行
  41. new Thread(td1).start();
  42. new Thread(td2).start();
  43. }
  44. }

1.2.2 处理死锁的几个常见的方法

预防死锁:
破坏死锁的四个必要条件中的一个或多个来预防死锁。
避免死锁:
和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的状态。
1、加锁顺序(线程按照一定的顺序加锁)因为当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。
2、加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
检测死锁:
运行时出现死锁,能及时发现死锁,把程序解脱出来
解除死锁:
发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。


Java面试的完整博客目录如下:Java笔试面试目录

转载请标明出处,原文地址:https://blog.csdn.net/weixin_41835916 如果觉得本文对您有帮助,请点击顶支持一下,您的支持是我写作最大的动力,谢谢。
这里写图片描述

发表评论

表情:
评论列表 (有 0 条评论,549人围观)

还没有评论,来说两句吧...

相关阅读

    相关 并发编程挑战

    一、并发编程的挑战 1 . 上下文切换 > CPU 通过给每个线程分配 CPU 时间片,并且不停的切换线程执行,让我们感觉到多个线程是同时执行的,所以任务从保存到再

    相关 使用并发编程挑战

    编程中使用多线程的目的是为了让程序执行的更快,效率更高。所以很多人想当然的认为多线程的执行效率一定比单线程的高。但在进行并发编程时,会发现试图使用多线程来提高程序的整体运行效

    相关 并发编程挑战

    由于最近在看《Java并发编程的艺术》,为了强迫自己看下去,就看着书,写着博客,也能把看书时自己的一些观点加入其中,方便理解和记忆。 Java并发编程的目的是为了让程序运行的