并发编程三要素
- 原子性
- 一个不可再被分割的颗粒,原子性指的是一个操作要么全部执行成功要么全部执行失败。
- 期间不能被中断,也不存在上下文切换,线程切换会带来原子性问题。
什么是上下文切换:上下文切换是指CPU从一个线程转到另一个线程时,需要保存当前线程的上下文状态,恢复另一个线程的上下文状态,以便于以下次恢复执行该线程时能够正确地运行。
在多线程编程中,上下文切换是一种常见的操作,上下文切换通常是指在一个CPU上,由于多个线程共享CPU时间片,当一个线程的时间片用完后,需要切换到另一个线程运行。此时需要保存当前线程的状态信息,包括程序计数器、寄存器、栈指针等,以便下次继续执行该线程时候能够恢复到正确的执行状态。同时,需要将切换到的线程的状态信息回复,以便于该线程能够正确运行。
- 有序性
- 程序执行的顺序按照代码的先后顺序执行,因为处理器可能会对指令进行重排序。
举个例子:1.创建订单 2.减少库存 3.创建物流订单,重排序可能会不按照123去执行,而是312执行。
- 可见性
- 一个线程A对共享变量的修改,另一个线程B能够立刻看到
常见的锁种类
- 悲观锁
- 当线程去操作数据的时候,总认为别的线程回去修改数据,所以每次它拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized、ReentrantLock
- 乐观锁
- 每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断。
- 如果数据被修改了就拒绝更新,比如CAS就是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步
- 比如数据库的乐观锁,通过版本控制来实现,乐观的认为在数据更新期间没有其他线程影响。
悲观锁适合写操作多的场景,乐观锁适合读操作的场景,乐观锁的吞吐量会比悲观锁多。
-
公平锁
- 指多个线程按照申请的顺序来获取锁,简单来说,如果一个线程组里,能保证每个线程都能拿到锁。
- 比如ReentrantLock(底层是同步队列FIFO:First Input First Output来实现)
-
非公平锁
- 获取锁的方式是锁机获取的,保证不了没哥线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁。
- 比如synchronized、ReentrantLock
-
独享锁(互斥锁)
- 也叫他排它锁/写锁/独占锁/独享锁/该锁每一次只能被一个线程所持有,枷锁后任何线程试图再加锁的线程会被阻塞直到当前线程解锁。例如:线程A对data1加上排他锁,则其他线程不能再对data1加任何类型的锁。
- 获得互斥锁的线程即能读数据又能修改数据
-
共享锁
- 也叫S锁/读锁,你那个查看但无法修改和删除的一种数据锁,加锁后其他用户可以并发读取,查询数据但不能修改、增加、删除数据,该锁可被多个线程所持有,用于资源数据共享。
-
可重入锁
- 也叫递归锁,在外层使用锁之后,在内存仍然可以使用,并且不发生死锁
-
不可重入锁
- 若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。
可重入锁能一定晨读的避免死锁,synchronized,ReentrantLock重入锁。
- 自旋锁
- 一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁匙放能够被成功获取,知道获取到锁才会退出循环,任何时候最多只能又一个执行单元获取该锁。
- 不回发生线程状态切换,一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU。
- 分段锁
- 并不是具体的一种锁,只是一种锁的设计,将数据分段上锁,把锁进一步细化粒度,可以提升并发量,当操作不需要更新整个数组的时候,就仅针对数组中的一项进行加锁操作,比如CurrentHashMap底层就用了分段锁。
- 死锁
- 两个或两个以上的线程在执行过程中,由于竞争资源或由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法让程序进行下去