并发编程的简单总结

并发编程

线程

CPU调度最小单位,一个具体的执行单元

多线程

一个进程内,允许有多个线程同时执行

提高CPU的利用率,增强程序功能

对硬件(CPU,内存,硬盘)要求提高

多线程访问共享资源,存在线程安全问题

线程创建方式

继承Thread,实现Runnable接口 run(),实现Callable接口 call()可以抛出异常,有返回值

线程状态

新建 start()

就绪 yield() CPU加载

运行 sleep() wait() join() IO 等待同步锁

阻塞 stop() 任务结束

死亡

并行与并发

单核CPU下实际上还是串行执行的

总结一句话:微观串行,宏观并行

并发:同一时间做多件事情

并行:同一时间做多件事情

多线程目的

解决性能问题

为什么多线程能提升性能?

性能本质的提升,就是榨取硬件的剩余价值(硬件利用率)

多线程并行问题

  • 安全性,性能(切换开销等)
  • 死锁
  • 可见性,有序性,原子性

Java内存模型(JMM)

为什么会出现?

硬件发展过程中,CPU、内存、I/O设备的速度差异

速度排序:CPU >> 内存 >> I/O

优化:

  • CPU增加缓存,均衡内存与CPU差异
  • 操作系统增加进程、线程以分时复用CPU,均衡 I/O 设备与 CPU的速度差异
  • 编译程序优化指令执行次序,使缓存能够得到更加合理运用
JMM

Java 内存模型(Java Memory Model,JMM)规范了 Java 虚拟机与计算机内存是如何协同工作的。只要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量(线程共享的变量)存储到内存和从内存中取出变量这样底层细节。

JMM每个线程都有私有的本地内存

不同线程之间无法直接访问对方工作内存中的变量,线程间的通信一般有两种方式进行,一是通过消息传递,二是共享内存。Java 线程间的通信采用的是共享内存方式,线程、主内存和工作内存的交互关系如下图所示:

在这里插入图片描述

并发编程核心问题——可见性,原子性,有序性

可见性

一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性

产生原因:对于如今的多核处理器,每颗 CPU 都有自己的缓存,而缓存仅仅对它所在的处理器可见,CPU 缓存与内存的数据不容易保证一致

在这里插入图片描述

解决办法:使用volatile关键字

原子性

一个或多个操作在 CPU 执行的过程中不被中断的特性,我们称为原子性**(线程切换带来的原子性问题)**

解决办法:锁

有序性

有序性指的是程序按照代码的先后顺序执行

产生原因:编译器为了优化性能,有时候会改变程序中语句的先后顺序

总结
  • 缓存导致的可见性问题
  • 线程切换带来的原子性问题
  • 编译优化带来的有序性问题

volatile关键字

共享变量被volatile修饰后

  • 保证了不同线程对这个共享变量的可见性
  • 禁止进行指令重排序
  • 不能保证对变量操作的原子性

如何保证原子性

“同一时刻只有一个线程执行”我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的那么就都能保证原子性了

synchronized 关键字,就是锁的一种实现,是独占锁/排他锁

synchronized 并不能改变 CPU 时间片切换的特点,只是当其他线程要访问这个资源时,发现锁还未释放,所以只能在外面等待

synchronized 一定能保证原子性,因为被 synchronized 修饰某段代码后,无论是单核 CPU 还是多核 CPU,只有一个线程能够执行该代码,所以一定能保证原子操作. synchronized 也能够保证可见性和有序性

JUC——原子变量

加锁是一种阻塞式方式实现,原子变量是非阻塞式方式实现

原子类的原子性是通过 volatile + CAS 实现原子操作的

AtomicInteger 类中的 value 是有 volatile 关键字修饰的,这就保证了 value的内存可见性

低并发情况下:使用 AtomicInteger

CAS

CAS(Compare-And-Swap) :比较并交换,该算法是硬件对于并发操作的支持

采用的是乐观锁和自旋锁,一种轻量级的锁机制

CAS 包含了三个操作数:

  1. 内存值 V
  2. 预估值 A (比较时,从内存中再次读到的值)
  3. 更新值 B (更新后的值)

每次将数据从中内存加载到工作内存 V

再次从内存中读取到值 A

当V==A,主内存数据没有其他线程修改,此时就把计算后的数据B写入到主内存中

是一种无锁实现方式,非阻塞式的 适合低并发

CAS缺点

该锁会不断循环判断,因此不会类似 synchronize线程阻塞导致线程切换,但是不断的自旋,会导致 CPU 的消耗

ABA问题:即某个线程将内存值由 A 改为了 B,再由 B 改为了 A。预期值与内存值相同,误以为该变量没有被修改过而导致的问题

解决ABA问题:添加版本号。每次修改后,更改版本号,可以通过比较版本号来判断值是否改变

ConcurrentHashMap

ConcurrentHashMap是线程安全的map

Hashtable也是安全的,直接将put方法整个枷锁,锁粒度大,效率低

ConcurrentHashMap放弃了分段锁,采用CAS原则+synchronized

在put()时,先判断添加的数据节点是不是第一个节点,如果是,采用CAS原则(判断比较),加入数据到第一个节点,如果不是第一个节点,会用第一个节点作为锁,这里加的锁是synchronized

放弃分段锁的原因:

  1. 加入多个分段锁浪费内存空间
  2. 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待
  3. 为了提高 GC 的效率

Java中锁的分类

不是不同的锁,只是锁的设计范式

乐观锁

对于同一个数据的并发操作,是不会发生修改的

悲观锁

对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改

公平锁

在分配锁前,检查是否有线程在排队等待获取该锁,优先将锁分配给排队时间最长的线程

非公平锁

在分配锁时不考虑线程排队等待的情况,直接尝试获取锁,在获取不到时再排到队尾等待

可重入锁

又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

读写锁(ReadWriteLock)
  1. 多个读者可以同时进行读
  2. 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
  3. 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
分段锁

分段锁并非一种实际的锁,而是一种思想,用于将数据分段,并在每个分段上都会单独加锁,把锁进一步细粒度化,以提高并发效率

自旋锁

自己重试,当线程抢锁失败后,重试几次,要是抢到锁了就继续,要是抢不到就阻塞线程。

目的是为了为了尽量不要阻塞线程

自旋锁是是比较消耗 CPU 的,因为要不断的循环重试,不会释放 CPU资源

共享锁

共享锁是指该锁可被多个线程所持有,并发访问共享资源。是读写锁中的读锁

独占锁

也叫互斥锁,是指该锁一次只能被一个线程所持有

AQS(AbstractQueuedSynchronizer)

抽象的队列式的同步器

没有获得到锁的其他线程,把他们加入到一个队列里(阻塞状态,CPU不会加载)

当锁被释放后,队列里的第一个线程获得到锁,按顺序排队获取锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ixIlN9iA-1627392575078)(C:\Users\lenovo\Pictures\Screenshots\AQS.PNG)]

锁的几种状态
  • 无锁状态

  • 偏向锁状态:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价

  • 轻量级锁状态:当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能

  • 重量级锁状态:当锁为轻量级锁的时候,并发访问量增多,锁状态升级为重量锁

Synchronized 实现

在synchronized修饰的程序块前后会添加一个监视器(进入,退出)利用对象头来记录锁是否被使用

获取锁,计数器+1,退出监视器,释放锁,计数器-1

特点:使用一个唯一的对象,作为锁状态的标记

ReenTrantLock

在内部有一个锁的状态默认是0,如果有线程获取到了锁,将状态改为1

其他线程有两种处理方式,公平锁和非公平锁

会阻塞,提高性能

  • 重量级锁状态:当锁为轻量级锁的时候,并发访问量增多,锁状态升级为重量锁

Synchronized 实现

在synchronized修饰的程序块前后会添加一个监视器(进入,退出)利用对象头来记录锁是否被使用

获取锁,计数器+1,退出监视器,释放锁,计数器-1

特点:使用一个唯一的对象,作为锁状态的标记

ReenTrantLock

在内部有一个锁的状态默认是0,如果有线程获取到了锁,将状态改为1

其他线程有两种处理方式,公平锁和非公平锁

如果使用公平锁,会将等待线程添加同步等待队列中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值