【Java编程的思想】并发总结

本文详细介绍了线程安全的各种机制,包括synchronized关键字、显式锁、volatile变量、原子变量和CAS、写时复制、ThreadLocal等。同时,还探讨了线程之间的协作机制,如wait/notify、显式条件、线程中断以及各种协作工具类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程安全的机制

线程表示一条单独的执行流,每个线程有自己的执行计数器,有自己的栈,但可以共享内存,共享内存是实现线程协作的基础,但共享内存有两个问题:竞态条件和内存可见性。

synchronized

synchronized是一个关键字,既可以解决竞态问题,也可以解决内存可见性问题

synchronized保护的是对象,而不是代码,只有对同一个对象的synchronized方法调用,synchronized才能保证它们被顺序调用。对于实例方法,这个对象是this;对于静态方法,这个对象是类对象;对于代码块,需要指定哪个对象

synchronized不能尝试获取锁,也不能响应中断,还可能会死锁。相比于显式锁,synchronized简单易用,JVM也在不断优化它的实现

显式锁

显式锁是相当于synchronized隐式锁而言的,它可以实现synchronized同样的功能,但需要程序自己创建锁,调用锁相关接口,主要接口是Lock,主要实现类是ReentrantLock。

相比synchronized,显式锁支持以非阻塞方式获取锁,可以响应中断,可以限时,可以指定公平性,可以解决死锁问题,所以更加的灵活。

在一些读多写少、读操作可以完全并行的场景中,可以使用读写锁以提高并发度,读写锁的接口是ReadWriteLock,实现类是ReentrantReadWriteLock

volatile

synchronized和显式锁都是锁,使用锁可以实现安全,但使用锁是有成本的,获取不到锁的线程还需要等待,会有线程的上下文切换开销等。
如果共享的对象只有一个,操作也只是进行最简单的get/set操作,set也不依赖于之前的值,那就不存在竞态条件问题,而只有内存可见性问题,这时,在变量的声明上加上关键字volatile就可以了。

volatile和synchronized的区别:

  1. volatile 仅能使用在变量级别; synchronized 则可以使用在实例方法、静态方法和代码块。
  2. volatile 仅能实现变量的修改可见性,并不能保证原子性;synchronized 则可以保证变量的修改可见性和原子性
  3. volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
  4. volatile 标记的变量不会被编译器优化; synchronized 标记的变量可以被编译器优化

原子变量和CAS

使用volatile,set的新值不能依赖于旧值,但很多时候,set的新值与原来的值有关,同时也不一定需要锁,这个时候就可以考虑原子变量。它们包含了一些以原子方式实现组合操作的方法。

原子变量的基础是CAS,一般的计算机系统都在硬件层次上直接支持CAS指令。相对于synchronized,它是乐观的,而synchronized是悲观的。

写时复制

之所有会有线程安全的问题,是因为多个线程并发读写同一个对象,如果每个线程读写的对象都是不同的,或者如果共享访问的对象是只读的,不能修改,那就不存在线程安全问题了。

写时复制就是将共享访问的对象变为只读的,写的时候再使用锁,保证只有一个线程写,写的线程不是直接修改原对象,而是新创建一个对象,对该对象修改完毕后,再原子性地修改共享访问的变量,让它指向新的对象。

ThreadLocal

ThreadLocal让每个线程对同一变量,都有自己的独有副本。每个线程实际访问的对象都是自己的,自然也就不存在线程安全问题。

线程的协作机制

常见的协作场景:生产者/消费者协作模式、主从协作模式、同时开始、集合点等

wait/notify

wait/notify与synchronized配合一起使用,是线程的基本协作机制。
每个对象都有一把锁和两个等待队列,一个是锁等待队列,放的是等待获取锁的线程;另一个是条件等待队列,放的是等待条件的线程。wait将自己加入条件等待队列,notify从条件队列上移除一个线程并唤醒,notifyAll移除所有线程并唤醒。

wait/notify方法只能在synchronized代码块内被调用,调用wait时,线程会释放对象锁,被notify/notifyAll唤醒后,需要重新竞争锁,获取到锁后才会从wait调用中返回。

显式条件

显式条件和显式锁配合使用,与wait/notify相比,可以支持多个条件队列,代码更为易读,效率更高。

线程中断

线程中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出,线程在不同状态和IO操作时对中断有不同的反应。

协作工具类

信号量Semaphore用于限制对资源的并发访问数

倒计时门闩CountDownLatch主要用于不同角色线程间的同步,比如:同时开始多个线程;主线程等待多个从线程的结果

循环栅栏CyclicBarrier用于同一角色线程间的协调一致,所有线程在到达栅栏后都需要等待其他线程,等所有线程都到达后再一起通过,它是可以循环的。

阻塞队列

阻塞队列封装了锁和条件,常用于生产者/消费者协作模式,只需要调用队列的入队/出队方法就可以了

  • 无锁非阻塞并发队列:ConcurrentLinkedQueue和ConcurrentLinkedDeque
  • 普通阻塞队列:基于数组的ArrayBlockingQueue,基于链表的LinkedBlockingQueue和LinkedBlockingDeque
  • 优先级阻塞队列:PriorityBlockingQueue
  • 延时阻塞队列:DelayQueue
  • 其他阻塞队列:SynchronousQueue和LinkedTransferQueue

Future/FutureTask

Future是一个接口,主要实现类是FutureTask。
Future封装了调用线程和执行线程关于执行状态和结果的同步,对于调用线程而言,它只需要通过Future就可以查询异步任务的状态、获取最终结果、取消任务等。
在常见的主从协作模式中,主线程往往需要获取子线程的结果,就可以使用Future

容器类

线程安全的容器有两类:同步容器;并发容器

同步容器

Collections类中有一些静态方法,可以基于普通容器返回线程安全的同步容器

public static <T> Collection<T> synchronizedCollection(Collection<T> c);
public static <T> List<T> synchronizedList(List<T> list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

它们是给所有容器方法都加上synchronized来实现安全的。
同步容器的性能比较低,这里的线程安全针对的是容器对象,指的是当多个线程并发访问同一个容器对象时,不需要额外的同步操作。

并发容器

写时复制的List和Set

CopyOnWriteArrayList实现了List接口,它的用法与其他的List基本是一样的。CopyOnWirteArrayList的内部也是 一个数组,但这个数组是以原子方式被整体更新的。每次修改操作,都会新建一个数组,复制原数组的内容到新数组,在新数组上进行需要的修改,然后以原子方式设置内部的数组引用。
CopyOnWriteArraySet实现了Set接口,不包含重复的元素。 内部是通过CopyOnWriteArrayList实现的

CopyOnWriteArrayList和CopyOnWriteArraySet适用于读远多于写、集合不太大的场景。它们是以优化读操作为目标的,读不需要同步,性能很高。

ConcurrentHashMap

ConcurrentHashMap是HashMap的并发版本,通过细粒度锁和其他技术实现了高并发,读操作完全并行,写操作支持一定程度的并行,以原子方式支持一些复合操作,迭代不用加锁。

基于跳表的Map和Set

Java并发包中与TreeMap/TreeSet对应的并发版本是ConcurrentSkipListMap和ConcurrentSkipListSet。

TreeSet是基于TreeMap实现的,类似地,ConcurrentSkipListSet也是以及ConcurrentSkipListMap实现的。

ConcurrentSkipListMap是基于SkipList实现的,SkipList称为跳跃表或跳表,是一种数据结构。

并发队列

  • 无锁非阻塞并发队列:ConcurrentLinkedQueue和ConcurrentLinkedDeque
  • 普通阻塞队列:基于数组的ArrayBlockingQueue,基于链表的LinkedBlockingQueue和LinkedBlockingDeque
  • 优先级阻塞队列:PriorityBlockingQueue
  • 延时阻塞队列:DelayQueue
  • 其他阻塞队列:SynchronousQueue和LinkedTransferQueue

无锁非阻塞是指,这些队列不实用锁,所有操作总是立即执行,主要通过循环CAS实现并发安全;
阻塞队列是指,这些队列使用锁和条件,很多操作都需要先获取锁或满足特点条件,获取不到锁或等待 条件时,会等待(阻塞),直到获取到锁或条件满足

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值