三个基础概念
原子性。一个操作或者一系列骚操作,要么全部执行要么全部不执行。(数据库中的“事物”就是个典型的原子操作)
可见性。当一个线程修改了共享属性的值,其它线程能立刻看到共享属性值的更改。(举个例子:由于JMM(Java Memory Model)分为主存和工作内存,共享属性的修改过程为从主存中读取并复制到工作内存中,在工作内存中修改完成之后,再刷新主存中的值。如果线程A在工作内存中修改完成但还没有刷新主存中的值,线程B看到的值还是旧值。这样可见性就没法保证)
有序性。程序的运行顺序似乎和我们编写逻辑的顺序是一致的,但计算机在实际执行中却并不一定。为了提高性能,编译器和处理器都会对代码进行重新排序。但是有个前提,重新排序的结果要和单线程执行程序顺序一致。
Java中控制并发的几种方式
- volatile (用来保证可见性和有序性,不保证原子性)
- synchronized
- CAS/AQS
- concurrent并发包
synchronized保证原子性、可见性和有序性。用来修饰方法或者代码块
根据锁对象的不同,一把锁同时最多只能被一个线程持有。
如果目标锁已经被当前线程持有,其它线程只能阻塞等待其它线程释放目标锁。
如果当前线程已经持有了目标锁,其他线程仍然可以调用目标类中没有被synchronized修饰的方法。
synchronized实现的是阻塞型并发,synchronized修饰的范围越大,瓶颈越高。为了解决这种问题,由此又有减小锁范围、减小锁粒度和锁分段之说。
synchronized锁住的代码块,同一时刻只能由一个线程访问。属于悲观锁。相对于这种需要挂起线程的悲观锁,还一种由CAS实现的乐观锁
CAS相对于synchronize,本质上也是一种阻塞的实现。只是阻塞的粒度(CPU指令级别)更小。
JDK在java/util/concurrent提供了很多常用的并发类及并发容器类。并发类基本是通过lock(CAS/AQS)实现,并发容器基本是通过synchronize和lock(CAS/AQS)实现的
各种锁来一波
- 独占锁:同一时刻只有一个线程持有同一锁,其余线程在链表中排队。
- 共享锁:同一时刻可以多个线程持有同一锁。
- 公平锁:锁被线程持有后,其余线程排队执行。锁按照FIFO放入链表。
- 非公平锁:锁被线程持有后,其余线程排队执行。锁按照FIFO放入链表。但是在刚释放锁的之后,如果有新线程竞争锁,那么新线程将和链表中下个即将被唤醒的线程竞争锁。