java并发编程知识要点总结

java并发编程

一、关于并发

  1. 并发是什么?

    • 并发是指在同一时间间隔内,有多个程序处于运行状态。当然,同一时刻只有一个程序在运行。与之对应的是并行,并行是指同一时刻有多个程序同时执行(宏观上)
  2. 为什么需要并发?
    • 为了提高系统的资源利用率 和 吞吐量。就好比全班需要填表时,可以发给每个人,然后填完之后在收起来,这样的效率远比一个一个的发,然后等第一个人填完了再发给第二人填写要快的多
  3. 什么是线程安全?
    • 线程作为独立调用的单位,当使用线程实现并发时,由于处理机的调度,可能存在线程安全问题。那什么是线程安全呢,定义为:当多个线程访问某个类是,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,这个类都能表现出正确的行为,则称这个类是线程安全的。

二、并发编程中的3个概念

1.原子性

  • 定义:一个操作要么全部操作 要么都不执行
  • 类似于 自增(++) 的操作,就不是原子的,因为这个操作实际上是一个“读取-修改-写入”的操作序列,其结果依赖于之前的状态。还有就是竞态条件(先检查后执行—if)非原子操作

2.可见性(同步)

  • 定义:可见性是指当一个线程修改了共享变量的值后,其他线程能够立即得知这个修改。为了确保多个线程之间对内存的写入操作的可见性,必须使用同步机制。在没有同步的情况下,编译器 或 处理器 都可能对操作的执行顺序进行调整。
  • volatile 变量,是一种稍弱的同步机制,它能确保变量的更新操作通知到其他线程。
    1. 非volatile变量为什么会存在不可见问题?

      • 因为变量存储在主内存中,当一个线程需要这个变量时,它会从主内存中拷贝一份到自己线程内存中。如果这时,这个线程对它进行了修改,其他线程又访问了这个变量时,也会去主内存进行拷贝,就会导致失效数据。
    2. volatile 变量是怎么保证可见性的呢?
      • 被 volatile 修饰的变量,每次在自己线程中使用时,都会从主内存中刷新自己的值(如果发生了改变),当被线程修改了值后,又会立即返回主内存中,把主内存中的值进行刷新。底层实现是通过内存屏障(lock前缀指令)实现的
    3. 那么 volatile 变量能保证原子性吗?
      • volatile 变量是不能保证原子性的

3.有序性

  • 有序性是指数据不相关的变量在并发的情况下,实际执行的结果和单线程的执行结果是一样的,不会因为重排序而导致结果未知,volatile、final、synchronized、显示锁可以保证有序性

三、同步机制

1.内置锁

  • 每个java对象都可以用做一个实现同步的锁,这些所被称为内置锁。获得内置锁的唯一途径就是进入这个锁保护的同步代码块或方法(被 synchronized 修饰的代码块或方法)。

    • 被synchronized 修饰的静态方法,锁对象为当前 Class 对象
    • 被 synchronized 修饰一般方法,锁对象为当前对象
    • 同步代码块的锁对象为自己指定的对象
  • synchronized 是 JVM 的内置属性,JVM对此进行了优化,比如:
    • 锁消除优化(去除一些不会发生竞争的锁);
    • 增加锁粒度(将附近的同步代码块用同一个锁合并起来,减少了同步的开销);
    • 找出不会发布到堆的本地对象引用,去除锁获取操作。(也就是线程封闭)
  • 内置锁是可重入的,为什么?
    • 重入是指:如果线程试图获取一个已经由它自己持有的锁,那么这个请求就会成功。
    • 为什么是可重入的?如下代码,当调用子类的 doSomething方法时,获取了锁(当前实例),然后调用父类的方法,如果不是可重入的,则会产生死锁,因为无法获得父类上的锁(也是当前实例,因为这个锁已经被持有,从而线程会永远停顿下去)
      
      public class Widget{
      
      public synchronized void doSomething(){
      
          ...
      
      }
      
      }
      
      public class LoggingWidget extends Widget{
      
      public sychronized void doSomething{
      
          super.doSomething();
      
      }
      
      }
      

2.显示锁Lock

a.关于Lock

  • Lock 是java中的一个接口,其提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作。与synchronized 相同的是 互斥性 与 内存可见性。当然,它也是可重入的。java.util.concurrent.locks包中有很多实现类,常用的有 ReentrantLock、ReadWriteLock,其实现都是依赖于 java.util.concurrent.AbstractQueuedSynchronizer 类
  • 下面是 Lock 接口 定义的方法
    
    public interface Lock{
    
    void lock();
    
    void lockInterruptily() throws InterruptedException;
    
    boolean tryLock();
    
    boolean tryLock(long time,TimeUnt unit) throws InterruptedException;
    
    void unlock();
    
    Condition newCondition();
    
    }
    
    /**
    
    其中前面4 个方法都是用来获取锁的,unLock()方法是用来释放锁的:
    
    1.lock()方法是用来获取锁的,如果锁已被其他线程获取,则进行等待。使用lock必须在try-catch 块中进行,释放锁必须在finally 块中进行,因为如果抛出了异常,这个锁永远都不会被释放。
    
    2.tryLock():请求加锁,如果加锁不成功返回false,不会发生阻塞
    
    3.tryLock(long time,TimeOut unit)throws InterruptedException:请求加锁,如果加锁不成功会在规定的时间内阻塞,超时后会返回,支持中断
    
    4.lockInterruptibly()throws InterruptedException:请求加锁,如果请求不到锁会发生阻塞,在阻塞过程中允许中断
    
    */
    

b.关于AbstractQueueSynchronizer

  • 在java.util.concurrent 中许多可阻塞类,例如 ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch、SynchronousQueue 和 FutureTask等都是基于 AQS 构建的。

3.在synchronized
和 ReentrantLock 的选择

  • synchronized 与 lock 锁的区别:

    1. synchronized 是 基于软件层面,JVM 内置的锁,而 Lock是基于CPU指令的
    2. synchronized 是基于块结构的,而 Lock 可以有更小的粒度
    3. synchronized 是不可中断的,而 Lock是可中断的
    4. synchronized 不用自己释放锁,而lock需要显示释放锁(在finally代码块中)
  • 由于 synchronized 是 JVM 的内置锁,所以对它可进行的干预很少,甚至不需要自己来释放锁。除此之外, JVM还对内置锁做了一些优化,所以建议只有在内置锁无法满足需求的情况下,才选择使用 ReentrantLock。比如:可定时、可轮询、可中断的锁获取操作,以及非块结构(相当于ConcurrentHashMap 中的锁分段)。

4.锁的劣势

  1. 当线程恢复执行时,必须等待其他线程执行完它们的时间片以后,才能被调度指向,在挂起和恢复线程等过程中存在很大的开销,并且通常存在着较长时间的中断
  2. 当一个线程正在等待锁时,它不能做任何其他事情,必须等待锁释放

5.非阻塞同步机制

  • 基于锁的劣势,处理器提供了一种类似于 volatile变量的机制同时还支持原子的更新操作(测试并设置,比较并交换,获取并递增)。这种方法采用的是乐观的方法,意思就是如果在操作过程中,有其他线程的干扰,就重试。
  • 比较并交换 CAS:首先从V中读取值A,并根据A计算新值B,然后再通过CAS以原子方式将V中的值由A变成B(只要在这段期间没有任何线程将 V的值修改为其他值)。这种方式的主要缺点是:它将是调用者处理竞争问题(重试、回退、放弃),并且在高并发的情况下,失败的几率大大增加。
  • 在java中,原子变量类就直接利用了底层的并发原语(CAS)来维持线程的安全性

四、避免使用同步的方式

  1. 线程封闭:也就是在单线程内访问数据

    • 局部变量,其封闭在执行的线程中,位于线程的栈中,其他线程无法访问这个栈
    • ThreadLocal 类,这个类能使线程中的某个值与保存值的对象关联起来,其可以防止对可变的单实例变量或全局变量进行共享。每个线程中有一个 localVariable 变量,这个变量是 一个threadLocalMap 变量,当设置值的时候会获取当前线程的 Map,然后把当前 threadLocal 添加进去,key为当前 threadLocal,value为 资源,然后获取的时候也是获取当前对象的Map,然后进行获取,从而屏蔽了线程的私有变量
  2. 不可变对象:因为不可变对象一定是线程安全。不可变对象必须满足一下条件:
    • 对象创建以后其状态就不能修改
    • 对象的所有域都是final类型
    • 对象是正确创建的(在对象的创建期间,this引用没有逸出)

五、并发容器 & 同步工具类

1.并发容器

  • Queue:

    • ConcurrentLInkedQueue:先进先出的并发队列
    • PriorityQueue:是一个非并发的优先队列,Queue上的操作不会阻塞,如果队列为空,获取元素的操作将会返回空值。是由LinkedList实现
    • BlockingQueue:可阻塞队列,如果队列为空,获取操作将一直阻塞,直到队列出现一个 可用的元素;如果队列已满,则插入元素的操作将一直阻塞,直到队列出现可用的空间。阻塞队列一般用于生产者-消费者模式
      • LinkedBlockingQueue:基于链表
      • ArrayBlockingQueue:基于数组
      • PriorityBlockingQUeue:按照优先级排序的队列
      • SynchronousQueue:其维护一组线程直接交付给处理线程,从而降低了将数据从生产者移动到消费者的延迟
  • Concurrent
    • CopyOnWriteArrayList| CopyOnWriteArraySet:写入时复制,也就是在每次修改时,都会创建并重新发布一个新的容器副本,因此写操作花费很大,但是在访问对象不需要进行同步
    • ConcurrentMap:是 Map的同步容器类
  • Deque:Deque是对Queue的扩展,它是一个双端队列,实现了在队列头和队列尾的高效插入和移除

    2.同步工具类

  • 闭锁

    闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行,用于等待事件

    CountDownLatch :使一个或多个线程等待一组事件发生,countDown方法递减计数器(表示还需等待多少事件发生),而await方法等待计数器达到零

  • FutureTask — 可用于实现缓存,用于检查某项任务是否已经开始,而不是检查是否完成

    Future.get 的行为取决于任务的状态,如果任务已经完成,那么 get会立即返回结果,否则get 将阻塞直到任务进入 完成状态,然后返回结果或者抛出异常。FutureTask 将计算结果从执行计算的线程传递到获取这个结果的线程

  • 信号量

    计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。技术信号量还可以用来实现某种资源池,或者对容器施加边界

    Semaphore 中管理者一组虚拟的许可,许可的初始数量可通过构造函数来指定,在执行操作时可以首先获得许可(只要还有剩余的许可 — 阻塞),并在使用后释放许可。当初始值为1 的 S恩吗炮轰热二值信号量可以用做互斥体,并具备不可重入的加锁(相当于的加锁)

    可以用做 数据库连接池 、 可以使用Semaphore 将任何一种容器变成有界阻塞容器

  • 栅栏 — 等待过后还要做其他事情

    闭锁是一次性对象,一旦进入终止状态,就不能被重置

    栅栏:所有线程必须同时到达栅栏位置才能继续执行,用于等待其他线程,等待过后还要做其他事情,用于实现一些协议

    ①CyclicBarrier:等待全部线程狙击,栅栏打开,线程被释放,栅栏被重置。线程如未全部通过,则线程将全部终止并抛出 BrokenBarrierException ,若成功通过,则 await将Wie每个线程返回一个唯一的到达索引号。

    完成了前一步的所有操作才可以进入下一步

    ②另一种形式的栅栏式 Exchanger ,它是一种两方栅栏,各方在栅栏位置上交换数据,当两个线程通过 Exchanger 交换对象时,这种交换就把这两个对象安全的发布给另一方。

    a. 当缓冲区被填满时,有填充任务进行交换,当缓冲区为空时,有清空任务进行交换

    b. 不仅当缓冲被填满时进行交换,并且当缓冲被填充到一定程度并保持一定时间后也进行交换

参考资料:

http://www.cnblogs.com/dolphin0520/p/3923167.html

时间: 2024-10-24 23:54:29

java并发编程知识要点总结的相关文章

Java并发编程知识总结

一.线程 1.线程创建: 继承Thread类创建线程类 实现Runnable接口创建线程类 使用Callable和Future创建线程 Runnable是执行工作的独立任务,但是它不返回任何值,如果希望任务完成时能够返回一个值,可以实现Callable接口 class TestThread implements Callable<Integer> { @Override public Integer call() throws Exception { return 1; } } //测试方法

《Java并发编程实战》要点笔记及java.util.concurrent 的结构介绍

买了<java并发编程实战>这本书,看了好几遍都不是很懂,这个还是要在实战中找取其中的要点的,后面看到一篇文章笔记做的很不错分享给大家!! 原文地址:http://blog.csdn.net/cdl2008sky/article/details/26377433 Subsections  1.线程安全(Thread safety) 2.锁(lock) 3.共享对象 4.对象组合 5.基础构建模块 6.任务执行 7.取消和关闭 8.线程池的使用 9.性能与可伸缩性 10.并发程序的测试 11.显

Java并发编程核心知识体系精讲

第1章 开宗明义[不看错过一个亿]本章一连串设问:为什么学并发编程?学并发编程痛点?谁适合学习本课?本课程包含内容和亮点?首先4大个理由告诉你为什么要学,其实源于JD岗位要求就不得不服了.其次5个痛点+12个亮点,是否说服你要死磕Java并发编程呢?... 第2章 跨越第一座山“线程八大核心”[适用于纵观全貌]八大核心-序章.从本章开始将带你攻克并发编程领域的“第一座大山”:多线程八大核心. 第3章 核心1:实现多线程的正确姿势[解读官方文档,够权威]相信很多小伙伴经常在各大技术博客或者论坛甚至

Java并发编程核心知识体系精讲 完整版

第1章 开宗明义[不看错过一个亿]本章一连串设问:为什么学并发编程?学并发编程痛点?谁适合学习本课?本课程包含内容和亮点?首先4大个理由告诉你为什么要学,其实源于JD岗位要求就不得不服了.其次5个痛点+12个亮点,是否说服你要死磕Java并发编程呢?... 第2章 跨越第一座山“线程八大核心”[适用于纵观全貌]八大核心-序章.从本章开始将带你攻克并发编程领域的“第一座大山”:多线程八大核心. 第3章 核心1:实现多线程的正确姿势[解读官方文档,够权威]相信很多小伙伴经常在各大技术博客或者论坛甚至

基于JVM原理JMM模型和CPU缓存模型深入理解Java并发编程

许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的Java开发工作中,仅仅了解并发编程的创建.启动.管理和通信等基本知识还是不够的.一方面,如果要开发出高效.安全的并发程序,就必须深入Java内存模型和Java虚拟机的工作原理,从底层了解并发编程的实质:更进一步地,在现今大数据的时代,要开发出高并发.高可用.考可靠的分布式应用及各种中间件,更需要深

6、Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil

读《Java并发编程的艺术》(一)

离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督促自己要不断的学习,不断的进步. 最近在进一步学习Java并发编程,不言而喻,这部分内容是很重要的.现在就以<并发编程的艺术>一书为主导线,开始新一轮的学习. 进程和线程 进程是一个应用程序在处理机上的一次执行过程,线程是进程的最小基本单位(个人理解).一个进程可以包含多个线程. 上下文切换 我们

Java并发编程学习路线

一年前由于工作需要从微软技术栈入坑Java,并陆陆续续做了一个Java后台项目,目前在搞Scala+Java混合的后台开发,一直觉得并发编程是所有后台工程师的基本功,所以也学习了小一年Java的并发工具,对整体的并发理解乃至分布式都有一定的提高,所以想和大家分享一下. 我的学习路线 首先说说学习路线,我一开始是直接上手JCIP(Java Concurrency in Practice),发现不是很好懂,把握不了那本书的主线,所以思索着从国内的作者开始先,所以便读了下方腾飞的<Java并发编程的艺

Java并发编程(一)

Java并发编程(一) 之前看<Thinking In Java>时,并发讲解的挺多的,自己算是初步了解了并发.但是其讲解的不深入,自己感觉其讲解的不够好.后来自己想再学一学并发,买了<Java并发编程实战>,看了一下讲的好基础.好多的理论,而且自我感觉讲的逻辑性不强.最后,买了本<Java并发编程的艺术>看,这本书挺好的,逻辑性非常强. 1. 概述 本篇文章主要内容来自<Java并发编程的艺术>,其讲解的比较深入,自己也有许多不懂的地方,然后自己主要把它讲