java并发学习51

目录

 

概念

进程和线程:

并发与并行

同步与异步

临界区

线程安全:

面试题:线程安全三大特性

java内存模型

创建多线程

1.继承thread 

2.runnable

3.Callable

多线程中的同步机制sync

线程的五种状态

线程池

CountDownLatch 倒计时锁

Semaphore 信号量

JUC  CyclicBarrier 循环屏障

CyclicBarrier使用场景

ReentrantLock 重入锁

Condition 条件唤醒

Callable & Future

JUC 同步容器

ConcurrentSkipListMap

JUC 之Atomic 包与CAS 算法

CAS 算法

总结

volatile

volatile和synchronized区别


概念

并发就是指程序同时处理多个任务的能力

并发编程的根源在于对多任务情况下对访问资源的有效控制

concurrent包:

调度器:ExecutorService

并发导致的问题:脏读

java程序:jar包或者war包

进程和线程:

进程是动态的概念,是程序运行的状态

线程是进程内的一个基本任务,每个线程都有自己的功能,是CPU分配和调度的基本单位。共享进程的资源

并发与并行

java并发学习51_第1张图片

同步与异步

java并发学习51_第2张图片

#请求之后必须等待得到响应,同步,是阻塞的

同步:拥有公共资源和共享数据的线程之间合理有序执行,合作完成某项任务的过程叫做同步

请求之后不用等待,系统会自动传入到回调函数,叫异步,是非阻塞的

临界区

临界资源用来表示一种公共资源与共享数据,可以被多个线程使用

访问临界资源的代码块叫临界区。

同一时间只能有一个线程访问临界区(阻塞状态),其他线程必须等待

死锁、饥饿、活锁

线程安全:

在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行。不会出现数据污染等意外情况。

多用户环境下结果和单用户结果应一致

面试题:线程安全三大特性

原子性:一个操作或者是多个操作,要不全部执行并且执行的过程中不会被任何因素打断,要么都不执行

可见性:当多个线程访问一个变量的时候,一个线程修改了变量的值,其他线程应立即能够看到。

有序性:如果在本线程内观察,所有操作都是有序的。如果在一个线程观察另一个线程,所有的操作都是无序的

用volatile防止重排序,但JVM已经有防止重排序的机制,所以用到的机会不多

java内存模型

java虚拟机内存

java并发学习51_第3张图片

当线程里的栈帧被销毁了之后,线程就执行完成了。就不会有栈里边的方法引用堆中的对象。所以堆中刚刚创建的对象就成了垃圾,需要回收。而方法区是静态区,里面还有常量区,不会被回收。虽然也有GC去扫描,但是明显频率很低

创建多线程

面试题:创建线程的三种方式

1.继承thread类创建线程

2.实现runnable 接口创建线程

3.使用callable future 创建线程

1.继承thread 

重写run方法,是具体线程的执行的方法

通过threadx.start() 启动线程

java并发学习51_第4张图片

垃圾回收的是监听线程

2.runnable

继承runnable接口重写run方法

但是不能直接.start(),要传给thread,让thread去启动线程

java并发学习51_第5张图片

3.Callable

jdk1.5 以后为我们专门提供了一个并发工具包 java.util.concurrent

java.util.concurrent 包含许多线程安全,测试良好,高性能的并发构建块。创建concurrent的目的就是要实现Collection框架对数据结构所执行的并发操作。通过提供一组可靠的,高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性,性能,可读性和可靠性。

java.util.concurrent.Callable

实现Callable接口可以允许我们的线程返回值或抛出异常

实现call方法的时候返回值和抛出异常

java并发学习51_第6张图片

要用到线程池去运行calllable对象

Future 用于接收线程内部call方法的返回值

java并发学习51_第7张图片

java并发学习51_第8张图片

不涉及临界区使用的可以用runnable,否则不推荐

多线程中的同步机制sync

synchronized(同步锁)关键字的作用就是利用一个特定的对象设置一个锁lock,在多线程并发访问的时候,同时只允许一个线程可以获得这个锁,执行特定的代码。执行后释放锁

如果两个同步代码块用同一块锁,那两个代码块也是互斥的,不能同时执行

面试题:synchronized 使用场景,对应不同的锁对象

synchronized代码块-- 任意对象即可

synchronized方法--this当前对象

synchronized静态方法--该类的字节码对象

如果用不同的同步代码块用同一把锁,那两个代码块是同步的。用不同的锁就是异步的

最常用的是锁方法

java并发学习51_第9张图片

synchronized 锁静态方法--用的锁是该类的字节码对象。

以下两个方法用的同一把锁----该类的字节码文件

java并发学习51_第10张图片

线程的五种状态

java并发学习51_第11张图片

java并发学习51_第12张图片

如果有数据共享的话那线程必须是安全的,否则可以不安全,毕竟不安全速度快。

java并发学习51_第13张图片

面试题

java并发学习51_第14张图片

线程池

对于new Thread() 创建线程,性能差,且缺乏统一管理,可能无限制的新建线程。相互竞争,严重时会占用过多系统资源导致死机或OOM

java并发学习51_第15张图片

concurrent 包中,提供了工具类Executors(调度器) 对象来创建线程池,可创建的有四种

CachedThreadPool  可缓存线程池  无限大,如果没有可用线程则创建,有空闲则利用起来

FixedThreadPool    定长线线程池   固定线程总数,空闲线程用于执行任务,如果没有多余线程,任务等待,等待算法为FIFO(先进先出)

SingleThreadExecutor    单线程池   只有一个线程

ScheduleThreadPool    调度线程池

java并发学习51_第16张图片

ScheduleThreadPool    调度线程池  适用于定时任务之类的

不常用,因为框架自带调度方法

java并发学习51_第17张图片

数据库的连接池是线程池的经典应用,每一个数据库连接都由线程来管理

CountDownLatch 倒计时锁

java并发学习51_第18张图片

java并发学习51_第19张图片

应用场景:写excel表,例如要写第10个表,第11个表是前十个sheet的汇总表,可以定义十个线程写10个sheet,同时用CountDownLatch 倒计时锁监听,当十个线程运行完后汇总 再输出第十一个表。

Semaphore 信号量

用于限制获取某种资源的线程数量

java并发学习51_第20张图片

即线程池中虽然有足够的线程,但是使用超过semaphore 也不可以运行。限制线程数为10,剩下的进不去的十个人要等前边的十个人玩完才可以获得线程执行run

下面是 当信号量满的情况下 直接退出不等待获得线程

java并发学习51_第21张图片

JUC  CyclicBarrier 循环屏障

就是解决了一个问题: 让所有的线程同一时刻开始执行,而不是用上述的循环方式创建多线程

CyclicBarrier 是一个同步工具类,它允许一组线程互相等待,直到到达某个公共屏障点。

与CountDownLatch(倒计时锁)不同的是该CyclicBarrier 在释放等待线程后可以重用

包含Semephore 的功能,可以限制同时运行的进程数。但是与Semephore 不同的是实现了线程复用,即不去创建新线程而是去用以前用过的空闲下来的线程

CyclicBarrier使用场景

适用于多线程必须同时开始的场景

java并发学习51_第22张图片

ReentrantLock 重入锁

重入锁是指任意线程在获取到锁之后再次获取该锁不会被该锁阻塞

ReentrantLock 设计的目标是用来代替synchronized关键字。但是还没实现。只会在面试的时候问到

java并发学习51_第23张图片

使用

java并发学习51_第24张图片

Condition 条件唤醒

java并发学习51_第25张图片

java并发学习51_第26张图片

java并发学习51_第27张图片

代码:四个线程用的是同一把锁,不会有并发并行问题。 那还用多线程有什么用呢????

java并发学习51_第28张图片

java并发学习51_第29张图片

java并发学习51_第30张图片

Callable & Future

java并发学习51_第31张图片

代码示例:

computer必须要实现callable接口,定义返回值类型为boolean

java并发学习51_第32张图片

JUC 同步容器

之前说的线程安全的类性能差,线程不安全的又不安全,如何鱼与熊掌兼得呢?

线程安全-并发容器

java并发学习51_第33张图片

java并发学习51_第34张图片

hashMap 是线程不安全的,hashTable 是线程安全的。concurrentHashMap 是线程安全的,效率高的

底层原理实现要搞清楚,面试要考

hashTable是整个table加锁,效率自然低,而CHM是分段加锁,段与段之间支持并发。段内要排队获取锁

CHM的速度 是hashTable 的16倍

java并发学习51_第35张图片

ConcurrentSkipListMap

对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树(B+树)。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 O(logn) 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。

跳表的本质是同时维护了多个链表,并且链表是分层的,

最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。有点像B+树

跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素18。

查找18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。

从上面很容易看出,跳表是一种利用空间换时间的算法。

使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。

JUC 之Atomic 包与CAS 算法

原子性:要么做完要么不做

JUC包下包含多个原子操作类

java并发学习51_第36张图片

都是基本数据类型的封装类,只不过具有原子性。这样我们在一些场景下就不用自己手动对数据加锁了。

Atomic类的底层应用的是CAS算法,所以只适合轻量级数据,计数器等

CAS 算法

是乐观锁最著名的实现算法

java并发学习51_第37张图片

java并发学习51_第38张图片

如果在并发很高的项目上用CAS, 效率不但不会提高反而会降低。因为一直有冲突一直要重试,非常占用计算资源

总结

java并发学习51_第39张图片

volatile

http://www.importnew.com/23535.html

java并发学习51_第40张图片

volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新的版本。所以volatile关键字的作用之一就是保证变量修改的实时可见性

volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

java并发学习51_第41张图片

volatile和synchronized区别

1、volatile不会进行加锁操作:

volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。

2、volatile变量作用类似于同步变量读写操作:

从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。

3、volatile不如synchronized安全:

在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。

4、volatile无法同时保证内存可见性和原则性:

加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。

当且仅当满足以下所有条件时,才应该使用volatile变量:

1、 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

2、该变量没有包含在具有其他变量的不变式中。

总结:在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的。尤其在、jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升。

你可能感兴趣的:(java,研发)