并发编程基础

基础知识

一、并发编程

并发编程的优点

  • 充分利用多核CPU的计算能力:
    通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。
  • 方便进行业务拆分,提升系统并发能力和性能:
    在特殊的业务场景下,先天的就适合与并发编程。现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分。

并发编程的缺点

并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能遇到很多问题,比如:内存泄漏、上下文切换、线程安全、死锁等问题。

并发编程三要素(线程的安全性问题的体现)

  • 原子性: 一个或多个操作要么全部执行成功要么全部执行失败。

  • 可见性: 一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized、 volatile)

  • 有序性: 程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

出现线程安全问题的原因 及解决办法

  • 线程切换带来的原子性问题
    JDK Atomic 开头的原子类、synchronized、LOCK 可以解决原子性问题
  • 缓存导致的可见性问题
    synchronized、volatile、LOCK 可以解决可见性问题
  • 编译优化带来的有序性问题
    Happens-Before 规则可以解决有序性问题

并行和并发有什么区别

  • 并发: 多个任务同一个 CPU 核上,按细分的时间片轮流执行,从逻辑上来看哪些任务时同时执行。
  • 并行: 单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的 “同时进行”。
  • 串行: 有n个任务,由一个线程按顺序执行。由于任务、方法都在 一个线程执行,所以不存在线程不安全情况,也就不存在临界区的问题。

什么是多线程 及多线程的优劣

多线程:
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同任务。

多线程的好处:
可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说,允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的劣势:

  • 线程也是程序,所以线程需要占用内存,线程越多占用的内存也越多。
  • 多线程需要协调和管理,所以需要 CPU 时间跟踪线程。
  • 线程之间对共享资源的访问会互相影响,必须解决竞争共享资源的问题。

线程和进程区别

进程
一个在内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中, 一个运行的 .exe 就是一个进程。
线程
进程中一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
根本区别
进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。

守护线程和用户线程

用户线程:
运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程。
守护线程:
运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。
一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作。

main 函数所在的线程就是一个用户线程,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

区别:
用户线程结束,JVM 退出,不管这时候有没有守护线程运行。
而守护线程不会影响 JVM 的退出。

什么是线程死锁

死锁
是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们将无法推进下去。 此时称为系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。

形成死锁的四个必要条件

  1. 互斥条件:
    进程(线程)对于所分配到的资源具有排它性,即一个资源只能被一个进程(线程)占用,直到被该进程(线程)释放。
  2. 请求与保持条件:
    一个进程(线程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:
    进程(线程)已获得的资源在未使用完之前不能被其它线程其强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件
    当发生死锁时,所等待的进程(线程)必定会形成一个环路,造成永久阻塞。

创建线程的四种方式

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 实现 Callable 接口
  • 使用 Executors 工具类创建线程池

二、线程安全

什么是线程安全

指某个方法在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使得程序功能正确完成。

在 Java 程序中怎么保证线程的运行安全

  1. 使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
  2. 使用自动锁 synchronized 关键字
  3. 使用手动锁 Lock java类

synchronized 和 Lock 有什么区别

  1. synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
  2. synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
  3. synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;
    而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
  4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

三、悲观锁和乐观锁

当出现多个用户同时执行更新等操作时,会出现事务交叉更新操作的冲突,会破坏业务和数据的完整性。可以使用悲观锁和乐观锁解决这类问题。

  1. 悲观锁机制:
    在进行数据查询时追加一个锁机制,进行业务操作,此时其他用户不能进行增删改操作,在事务结束时会自动将锁释放,其他用户可以继续执行此类操作。

悲观锁特点:
将用户操作一个一个处理,可以解决更新并发问题,缺点是处理效率比较低。

  1. 乐观锁机制:
    多个不同用户都可以同时对数据库记录进行查看和更新操作,但是最先commit提交的用户会执行成功,后续用户会以异常形式提示失败。

乐观锁特点:
允许多个用户同时操作,处理效率相对较高。

乐观锁使用步骤:

  1. 将原有数据表追加一列版本字段,初始值0
  2. 在实体类中添加版本属性
  3. 在映射描述文件中采用元素定义版本属性和版本字段的映射
  4. 当发生多个事务并行交叉执行时,第一个提交的成功,后续提交的会抛出异常。可以异常捕获给用户一个友善的提示。

四、AQS(AbstractQueuedSynchronizer)

  • 这个类在 java.util.concurrent.locks 包下面。
  • AQS 是一个用来构建锁和同步器的框架,
    AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就绪要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是 用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
  • CLH 队列是一个虚拟的双向队列。AQS 是将每条请求共享资源的线程封装成一个CLH 锁队列的一个结点(Node)来实现锁的分配。

五、并发容器

ConcurrentHashMap

CopyOnWriteArrayList

ThreadLocal

BlockingQueue

ConcurrentLinkedQueue

六、线程池

事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

Executors 类创建四种常见线程池

  1. newSingleThreadExecutor
    创建一个单线程的线程池
  2. newFixedThreadPool
    创建固定大小的线程池
  3. newCachedThreadPool
    创建一个可缓存的线程池
  4. newScheduledThreadPool
    创建一个大小无限的线程池

线程池的优点

  1. 降低资源消耗:
    重用存在的线程,减少对象创建销毁的开销。
  2. 提高响应速度:
    可以有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免阻塞。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性:
    线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配,调优和监控。
  4. 附加功能:
    提供定时执行、定期执行、单线程、并发数控制 等功能。

综上所述:
使用线程池框架 Executor 能更好的管理线程、提供系统资源使用率。

Executor 框架

提供线程的抽象,基于生产者–消费者模式
线程池是线程的所有者

什么是 以及 为什么使用 Executor 框架
  • 是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架。
  • 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的,而且无限制的创建线程会引起应用程序内存溢出。
  • 所以创建一个线程池是个更好的解决方案,因为可以限制线程的数量,并且可以回收再利用这些线程。利用 Executor 接口可以非常方便的创建一个线程池。
在 Java 中 Executor 和 Executors 的区别
  • Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。

  • Executor 接口对象能执行我们的线程任务。

CAS

以上均为基于锁的阻塞的原子考虑。而CAS (compare and swap)比较并交换,是针对当下流行的多处理器而设计,是乐观技术,即多个线程尝试使用CAS同时更新时,只有一个能更新成功,且不会挂起。

高并发和并发编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值