多线程——面试中一个常考内容(13)

张开发
2026/4/15 3:26:13 15 分钟阅读

分享文章

多线程——面试中一个常考内容(13)
这次是多线程的最后的内容了。上次提到了JUC的一些组件现在我们继续。CountDownLatch使用多线程经常把一个大的任务拆分成多个子任务使用多线程执行这些子任务从而提高程序的效率。那么如何衡量这多个子任务的完成情况呢整个任务都完成又怎么表示我们可以1CountDownLatch构造方法指定参数描述拆成了多少个任务这里拆成了10个子任务2每个任务执行完毕之后都调用一次countDown方法当一共调用了10次说明任务都完成了3主线程中调用await方法等待所有任务执行完毕await就会返回或阻塞等待这里的shutdown的作用包括上次漏掉的get操作多线程下使用ArrayList1.自行加锁推荐分析清楚要把哪些代码打包到一起成为一个“原子”操作。2.不推荐返回的List的各种关键方法都是带有synchronized3.使用CopyOnWriteArrayList这个偏向于不去加锁是编程中的一种常见的思想方法写时拷贝。修改不同变量或读取变量时这里要把数组中的元素1、2、3、4分别修改为元素100、200、300、400一开始引用指向原数组多线程先是读取一旦某个线程进行写操作比如修改1为100复制一个与原数组元素相同的数组在那里修改复制过程中如果其他线程在读就直接读取旧版本的数据。虽然复制过程不是原子的消耗一定的时间由于提供了旧版本的数据不影响其他线程读取。当新版本数组复制完毕之后直接进行引用的修改引用的赋值是“原子的”。这样就能确保读取过程中要么读到的是旧版数据要么读到的是新版数据不会读到修改一半的数据如100,200,3,4.但是也有缺点1.数组特别大非常低效2.如果多个线程同时修改也容易出问题多线程使用哈希表也是三种方法1HashMap线程不安全2HashTable线程安全给各种public方法加synchronized不推荐3ConcurrentHashMap这个效率更高按照桶级别进行加锁而不是给整个哈希加一个全局锁有效降低冲突的概率。哈希表中我们都知道在若干个哈希桶中将key的值映射到数组的下标中可是当不同的key映射到同一个下标中时便产生了哈希冲突。具体解决方式有两种第一种是线性探测这个就是另一个key的值映射到下一个下标中这个方法基本不用第二种就是使用链表在每个哈希桶上挂一个链表如果链表太长了要么扩容要么将链表变为红黑树。此时Hashtable中是对应整个数组加锁this指向的是整个数组此时任意两个线程访问任意两个不同的元素都会产生锁竞争。可是如果修改的两个元素在不同的链表上本身就不涉及线程安全问题修改不同变量如果修改同一个链表上的两个元素可能有线程安全问题比如把这两个元素插入到同一个元素后面就可能产生竞争。于是我们不妨在每个哈希桶上都分别加一个不同的锁这时如果修改不同链表上的元素针对不同的锁对象加锁就不会产生锁竞争不会阻塞。在实际开发中用到的Hash表可能性比较大即使多线程上访问上述的哈希表同一时刻两个线程恰好访问同一个链表的可能性概率就比较低。有人问加了这么多锁难道不会影响性能吗其实分为时间开销与空间开销。Java任意一个对象都可以作为锁对象在这个逻辑中不需要额外创建这么多对象作为锁直接使用每个链表的头节点作为synchronized的锁对象就行了。于是空间开销很小时间开销虽然比HashMap大因为他不加锁但比起Hashtable来还是绰绰有余的。接下来就是ConcurrentHashMap对这些的核心优化点了。1把对整个表加锁变为只给桶加锁2使用原子类针对size一个链表上插入元素另一个链表上也插入元素进行维护3针对哈希扩容的场景化整为零、确保每个操作的加锁时间不会太长扩容操作意味着需要创建更大的数组把就哈希表中的所有元素搬运到新的哈希中此时元素很多耗时很长。假设某个插入操作触发了扩容进行搬运了搬运过程中就需要更长时间的加锁了。一口气进行所有的搬运比较耗时那么我们把整个的搬运拆成多次来完成一旦触发扩容不是通过一次put来完成的而是通过多次put/get等操作完成的。历时将近两周终于把多线程讲完了大家好好消化一下。我的gitee链接https://gitee.com/QQ2240635095/java4_11.githttps://gitee.com/QQ2240635095/java4_12.git

更多文章