当前位置:   article > 正文

Java之并发编程_java并发编程

java并发编程

线程回顾

线程基本概念:

​ 线程:线程是程序处理的基本最小单位,是CPU执行的单元

​ 程序:静态的代码

​ 进程:运行中的程序,被加载到内存中,是操作系统分配内存的基本单位

线程的创建方式

​ 类 继承 Thread 重写run() 创建类的对象

​ 实现Runnable接口 重写run() 任务 new Thread(任务)

​ 实现Callable接口 重写call() 有返回值,可以抛出异常

常用的方法

​ run()

​ start()

​ 构造方法

​ 获取当前线程

​ 获取名字

​ sleep()

​ join()

​ yield()

​ 设置优先级

​ wait()

​ notify()

​ notifyAll()

线程状态:

​ 创建 --start()-- 就绪 ---- 运行 ----- 死亡

​ ----阻塞----

守护线程

多线程

什么是多线程

​ 一个程序中,支持同时运行多个线程

多线程的优点

​ 提高程序响应速度,提升硬件(cpu)利用率

多线程的问题

​ 线程过多占内存

​ cpu需要处理线程,需要性能能够满足

​ 多线程访问同一个资源

什么是并发编程

并行:在同一个时间节点上,同时发生(是真正意义上的同时执行)

并发:在一段时间内,对某个事情交替执行

并发编程:在例如抢票,抢购,秒杀等等场景下,有大量的请求访问同一个资源。会出现线程安全的问题,所以需要通过编程来控制解决让多个线程依次访问资源,称为并发编程。

并发编程的根本原因

多核cpu

JMM java内存模型

​ java内存模型,是java虚拟机规范的一种工作模式

​ 将内存分为主内存和工作内存

​ 变量数据存储在主内存中,线程在操作变量时,会将主内存中的数据复制一份到工作内存。

​ 在工作内存中操作完成后,再写回到主内存中。

多线程核心根本问题

基于java内存模型的设计,多线程操作一些共享的数据时,会出现以下3个问题

不可见性:多个线程分别同时对共享数据操作,彼此之间不可见,操作完写回主内存,有可能出现问题。

无序性:为了性能,对一些代码指令的执行顺序重排,以提高速度。

非原子性:

解决办法:

​ 让不可见 变为可见

​ 让无序变为 有序/不重排

​ 非原子性 变为原子(加锁)由于线程切换执行导致

volatile 关键字

​ volatile修饰的变量被一个线程修改后,可以在其它线程中立即可见

​ volatile修饰的变量在执行的过程中不会被重排序执行。

​ volatile不能解决原子性问题

缓存(工作内存)带来了不可见性

指令重排优化 带来了无序性

线程切换带来了 非原子性

volatile 底层实现原理

​ 在底层指令级别来进行控制

​ volatile修饰的变量在操作前,添加内存屏障,不让它的指令干扰

​ volatile修饰的变量添加内存屏障之外,还要通过缓存一致性协议(MESI)将数据写回到主内存,其它工作内存嗅探后,把自己工作内存数据过期,重新从主内存读取最新的数据。

如何保证原子性

只有通过加锁的方式让线程互斥执行来保证只有一个线程对共享资源访问。

synchronized: 关键字 修饰代码块,方法 自动获取锁,自动释放锁

ReentrantLock: 类 只能对某段代码进行修饰 需要手动加锁,手动释放锁

在java中还提供一些原子类,在低并发情况下使用,是一种无锁实现。

采用CAS机制(CAS(Compare-And-Swap) 比较并交换)

​ 是一种无锁实现。在低并发情况下使用。

采用自旋思想:

​ 第一次获取内存值到工作内存中,存储起来作为预期值,

​ 然后对象数据进行修改

​ 将工作内存中值写入主内存,在写入之前需要做一个判断,用预期值与主内存中的值进行比较。

​ 如果预期值与主内存中值一致,说明没有其它线程修改,将更新数的值,写入到主内存。

​ 如果预期值与主内存值不一致,说明其它线程修改了主内存的值,这时就需要重复操作整个过程。

特点:

​ 不加锁,所有的线程都可以对共享数据操作。

​ 适合低并发时使用。

​ 由于不加锁,其它线程不需要阻塞,效率高。

缺点:大并发时,不停自旋判断,导致cpu占用率高。

​ ABA问题:通过设置版本号,每次操作改变版本号。

java中的锁分类

synchronized: 关键字 修饰代码块,方法 自动获取锁,自动释放锁

ReentrantLock: 类 只能对某段代码进行修饰 需要手动加锁,手动释放锁

java中锁的名词

​ 一些锁的名称指的是锁的特性,设计,状态,并不是都是锁。

乐观锁:认为并发的操作,不加锁的方式实现是没有问题的,每次操作前判断(CAS,自旋)是否成立,不加锁实现。

悲观锁: 认为并发的操作肯定会有问题,必须加锁,是加锁的实现。

可重入锁: 当一个线程获取到外层方法的同步锁对象后,可以获取到内部其它方法的同步锁。

读写锁: ReentrantReadWriteLock 支持读,写加锁

​ 如果都是读操作,就不加锁

​ 如果一旦有写操作,读写互斥

分段锁: 并非是一种实际的锁,是一种锁实现的思想,将锁的粒度拆分,提高效率

自旋锁: 不是锁,是以自旋的方式重试获取

共享锁:读写锁中的读锁就是共享锁,读读是不互斥的,共享

独占锁: 互斥锁 synchronized ReentrantLock 属于独占锁

公平锁(Fair Lock): 就是可以根据线程的先来后到公平的获取锁,例如 ReentrantLock就可以实现公平锁。

非公平锁(Nonfair Lock): 没有先来后到,谁抢到谁获得执行权, ReentrantLock也可以实现非公平锁,synchronized 是非公平锁

synchronized锁的底层实现中,提供锁的状态,又来区别对待。

这个锁的状态在同步锁对象的对象头中,有一个区域叫Mark Word中存储

无锁状态

偏向锁状态: 一直是一个线程访问 记录线程的id 快速的获取锁

轻量级锁状态:当锁的状态为偏向锁时,又继续有其他的线程来访问,此时升级为轻量锁,没有获取到锁的线程,不会阻塞,继续不断尝试获取锁。

重量级锁状态: 当锁的状态为轻量级锁时,线程自旋达到一定的次数,进入到阻塞状态,锁状态就升级为重量级锁,等待操作系统调度。

synchronized 锁实现

synchronized锁是依赖底层编译后的指令来控制的,需要我们提供一个同步对象,来记录锁状态。

对象头 hashCode,垃圾收集年龄,锁状态…

AQS (AbstractQueuedSynchronizer)

抽象同步队列是JUC其它锁实现的基础。

思路:

​ 在类中维护一个state变量,然后还维护一个队列,以及获取锁,释放锁的方法。

​ 当线程创建后,先判断state的值,如果为0,没有线程使用,state = 1,执行完成后,再将state = 0

​ 期间如果有其它的线程访问,state = 1,将其它线程放入队列中。

ReentrantLock

非公平

//NonfairSync
final void lock() {
            if (compareAndSetState(0, 1))// 线程到达后,直接尝试获取锁,是非公平的
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

公平实现

//FairSync
final void lock() {
            acquire(1);
        }
  • 1
  • 2
  • 3
  • 4

JUC中的常用类

ConcurrentHashMap

ConcurrentHashMap对于多线程,介于HashMap(线程不安全的,不能在多线程下使用) 与 Hashtable(线程安全的,加锁方式是直接锁住整个方法,效率低) 之间。内部采用“锁分段”机制(jdk8 弃用了分段锁,使用 cas+synchronized)替代 Hashtable 的独占锁。进而提高性能。

不支持存储 null 键和 null 值.因为无法分辨是 key 没找到的 null 还是有 key 值为 null

ConcurrentHashMap 线程安全的实现类,效率高于Hashtable

Hashtable和ConcurrentHashMap 键值都不能为空

CopyOnWriteArrayList

ArraayList 是线程不安全的,在高并发情况下可能会出现问题,

Vector 是线程安全的。但是get方法也加了锁,读操作多的情况下,效率低。

使用CopyOnWriteArrayList 在读的时候不加锁,写的时候加锁,提高读的效率

在进行add,set等修改操作时,先将数据进行备份,对备份的数组进行修改,之后将修改后的数据赋值给原数组.

CopyOnWriteArraySet

CopyOnWriteArraySet 的实现基于 CopyOnWriteArrayList,不能存储重复数据。

辅助类CountDownLatch

CountDownLatch 允许一个线程等待其他线程各自执行完毕后再执行。底层实现是通过AQS 来完成的.创建 CountDownLatch 对象时指定一个初始值是线程的数量。每当一个线程执行完毕后,AQS 内部的 state 就-1,当 state 的值为0 时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

对象引用

强引用,默认的引用类型 即 对象由引用指向的,Object obj=new Object();这种情况下,new出来的对象不能被垃圾回收。

软引用,弱引用,虚引用都是用来标记对象的一种状态。

当一些对象称为垃圾后,还需要有不同的状态,可以继承Soft ReferenceWeak ReferencePhantom Reference

或者把自己的对象添加到软、弱、虚的对象中。

​ 如果内存充足的情况下,可以保留软引用对象

​ 如果内存不足,经过一次垃圾回收后,仍然不够,那么将清除软引用对象

​ 弱引用管理的对象,只能存活到下一次垃圾回收。

​ 和没有任何引用是一样的,只是为了系统的监测。

线程池

数据库连接池

池的概念

​ 频繁的创建数据库链接对象,销毁,在时间上开销较大。

​ 集合:事先创建出一些连接对象,每次使用时,从集合中直接获取,用完不销毁,减少频繁创建,销毁。

在jdk5之后,提供线程池的实现。

使用ThreadPoolExecutor类实现线程池创建管理

池的好处:减少频繁创建销毁时间,统一管理线程,提高速度。

ThreadPoolExecutor

7个参数:

corePoolSize: 核心线程池的大小

maximumPoolSize: 线程池最大数量

keepAliveTime: 非核心线程池中的线程,在多久没有任务执行时,就终止

unit: 为keepAliveTime设定单位

workQueue:一个阻塞任务队列,用来存储等待的任务。

​ ArrayBlockingQueue:有界的阻塞队列,必须给定最大容量。

threadFactory:线程池工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略,核心线程池,阻塞队列,非核心线程池已满,继续有任务,如何执行。
	AbortPolicy策略:抛出异常,拒绝执行。
	DiscardOleddestPolicy策略:丢弃等待时间最长的任务。
	DiscardOPolicy策略:直接丢弃,不执行
	CallerRunsPolicy策略:交由当前提交任务的线程执行
  • 1
  • 2
  • 3
  • 4
  • 5

execute 与 submit 的区别

​ execute提交任务,没有返回值

​ submit 提交任务,可以有返回值

关闭线程池

shutdownNow()直接关闭

shutdown()不再接收任务,等待任务执行完关闭

ThreadLocal

本地线程变量,可以为每个线程创建一个变量副本,使得多个线程之间相互隔离互不影响。

ThreadLocal底层实现:

​ 为每个当前线程创建了一个ThreadLocalMap,唯一的ThreadLocal对象作为key。

ThreadLocal内存泄漏问题:

​ 因为ThreadLocal与弱引用有关,key失效后,value还被强引用,造成内存泄漏。正确的用法,用完之后,及时调用remove()清除。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/926065
推荐阅读
相关标签
  

闽ICP备14008679号