Java并发篇

作者:星晴(当地小有名气,小到只有自己知道的杰伦粉)

1. Java锁

1.1 乐观锁

1.2 悲观锁

1.3 自旋锁

1.4 Synchronized 同步锁

1.4.1 核心组件

  • Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;

  • Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;

  • Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;

  • OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;

  • Owner:当前已经获取到所资源的线程被称为 Owner;

  • !Owner:当前释放锁的线程

1.4.2 实现

1.5 ReentrantLock

1.6 Semaphore

1.7 锁的状态

锁状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁

1.7.1 轻量级锁

   “轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,
轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量
级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场
景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀
为重量级锁。

1.7.2 偏向锁

    Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线
程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起
来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级
锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换
ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所
以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。上面说过,轻
量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进
一步提高性能。

1.7.3 重量级锁

    Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又
是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用
户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么
Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为
“重量级锁”。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。
JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和
“偏向锁”。

1.8 锁升级

1.9 锁优化

  • 减少锁持有时间:只用在有线程安全要求的程序上加锁

  • 减小锁粒度:ConcurrentHashMap

  • 锁分离:最常见的锁分离就是读写锁 ReadWriteLock

  • 锁粗化:如果对同一个锁不停的进行请求、同步 和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。

    举例:
    for(int i=0;i<size;i++){
      synchronized(lock){
      }
    }
    需要锁粗化:
    synchronized(lock){
      for(int i=0;i<size;i++){
      }
    }
  • 锁消除:锁消除是发生在编译器级别的一种锁优化方式。有时候我们写的代码完全不需要加锁,却执行了加锁操作。

2.线程

2.1 线程基本方法

线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等

2.1.1 线程等待wait

调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的

是调用 wait()方法后,会释放对象的锁。因此,wait 方法一般用在同步方法或同步代码块中

2.1.2 线程睡眠sleep

sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,sleep(long)会导致

线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态

2.1.3 线程让步yield

yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,

优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对

线程优先级并不敏感。

2.1.4 线程中断interrupt

中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这

个线程本身并不会因此而改变状态(如阻塞,终止等)。

2.1.5 Join 等待其他线程终止

join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞

状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。

2.1.6 线程唤醒 notify

Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象

上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调

用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继

续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞

争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程

2.2 线程上下文切换

巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存

下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载, 这段过程就叫做

上下文切换。时间片轮转的方式使多个任务在同一颗 CPU 上执行变成了可能

2.2.1 进程

指一个程序运行的实例。

2.2.2 上下文

是指某一时间点 CPU 寄存器和程序计数器的内容

2.2.3 寄存器

是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内

存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速

度。

2.2.4 程序计数器

是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令

的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统

3.线程池

3.1 线程池原理

3.2 线程池的组成

分为4个组成部分:

  1. 线程池管理器:用于创建并管理线程池

  2. 工作线程:线程池中的线程

  3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行

  4. 任务队列:用于存放待处理的任务,提供一种缓冲机制

4.JAVA 阻塞队列

  • ArrayBlockingQueue :由数组结构组成的有界阻塞队列。

  • LinkedBlockingQueue :由链表结构组成的有界阻塞队列。

  • PriorityBlockingQueue :支持优先级排序的无界阻塞队列。

    是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现
    compareTo()方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue 时,指定构造
    参数 Comparator 来对元素进行排序。需要注意的是不能保证同优先级元素的顺序
  • DelayQueue:使用优先级队列实现的无界阻塞队列。

    1.缓存系统的设计
    2.定时任务调度
  • SynchronousQueue:不存储元素的阻塞队列。

        是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。
    SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线
    程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给
    另 外 一 个 线 程 使 用 , SynchronousQueue 的 吞 吐 量 高 于 LinkedBlockingQueue 和
    ArrayBlockingQueue。
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列。

  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列

5.CyclicBarrier、CountDownLatch、Semaphore的用法

5.1 CountDownLatch(线程计数器 )

CountDownLatch 类位于 java.util.concurrent 包下,利用它可以实现类似计数器的功能。比如有

一个任务 A,它要等待其他 4 个任务执行完毕之后才能执行,此时就可以利用 CountDownLatch

来实现这种功能了

final CountDownLatch latch = new CountDownLatch(2);
    new Thread(){public void run() {
     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
     Thread.sleep(3000);
     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
     latch.countDown();
    };
        }.start();
     new Thread(){ public void run() {
     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
     Thread.sleep(3000);
     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
     latch.countDown();
  };
                }.start();
     System.out.println("等待 2 个子线程执行完毕...");
     latch.await();
     System.out.println("2 个子线程已经执行完毕");
     System.out.println("继续执行主线程");
}

5.2 CyclicBarrier(回环栅栏-等待至 barrier 状态再全部同时执行)

字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环

是因为当所有等待线程都被释放以后,CyclicBarrier 可以被重用。我们暂且把这个状态就叫做

barrier,当调用 await()方法之后,线程就处于 barrier 了。

5.3 Semaphore(信号量-控制同时访问的线程个数)

6.volatile(变量可见性、禁止重排序)

  • 禁止重排序

  • 变量可见性

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有

多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPU

cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache

这一步。

7.CAS

优点:解决synchronized的资源消耗过重

缺点:

  • CPU开销较大

  • 不能保证代码块的原子性

  • ABA问题

8.AQS(同步器AbstractQueuedSynchronizer)

AQS 定义了一套多线程访问 共享资源的同步器框架

9.Unsafe

关注公众号,有更多好玩的等着你!!!

Java并发篇的更多相关文章

  1. 转: 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679 在<Java并发编程学习笔记之五:volatile变量修饰符-意料之外 ...

  2. 图学java基础篇之并发

    概述 并发处理本身就是编程开发重点之一,同时内容也很繁杂,从底层指令处理到上层应用开发都要涉及,也是最容易出问题的地方.这块知识也是评价一个开发人员水平的重要指标,本人自认为现在也只是学其皮毛,因此本 ...

  3. 【死磕Java并发】—–J.U.C之AQS(一篇就够了)

    [隐藏目录] 1 独占式 1.1 独占式同步状态获取 1.2 独占式获取响应中断 1.3 独占式超时获取 1.4 独占式同步状态释放 2 共享式 2.1 共享式同步状态获取 2.2 共享式同步状态释放 ...

  4. Java并发编程入门,看这一篇就够了

    Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容.这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉 ...

  5. Java并发 - (无锁)篇6

    , 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 本文主要介绍了死锁的概念与一些相关的基础类, 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 无锁是一种乐观的策略, 它假设对资源的访问是没 ...

  6. Java并发编程之CAS第一篇-什么是CAS

    Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...

  7. Java并发编程之CAS第三篇-CAS的缺点及解决办法

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

  8. java并发编程系列原理篇--JDK中的通信工具类Semaphore

    前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...

  9. 8成以上的java线程状态图都画错了,看看这个-图解java并发第二篇

    本文作为图解java并发编程的第二篇,前一篇访问地址如下所示: 图解进程线程.互斥锁与信号量-看完还不懂你来打我 图形说明 在开始想写这篇文章之前,我去网上搜索了很多关于线程状态转换的图,我惊讶的发现 ...

随机推荐

  1. python获取主机IP,主机名

    获取主机内网,外网IP,主机名 代码如下: #!/usr/bin/env python #-*- coding:utf-8 -*- # author:Zeng Xianhe import socket ...

  2. 00_01_使用Parallels Desktop创建WindosXP虚拟机

    打开paralles软件,选择文件->新建 继续 选择手动选择,之后勾选没有指定源也继续 选择要创建的操作系统(这里以XP为例,其他的windows系统安装基本都差不多) 根据需要选择,这里选择 ...

  3. Python游戏编程入门 中文pdf扫描版|网盘下载内附地址提取码|

    Python是一种解释型.面向对象.动态数据类型的程序设计语言,在游戏开发领域,Python也得到越来越广泛的应用,并由此受到重视. 本书教授用Python开发精彩游戏所需的[]为重要的该你那.本书不 ...

  4. c++ 第二天 命名空间、数组

    C++ 命名空间 命名空间,也就是名称空间/名字空间,注意需要的头文件是 iostream ,而不是 iostream.h ,后者是旧版本的 C++ 头文件,并不支持命名空间. 为什么要使用命名空间? ...

  5. PHP 循环 - While 循环

    PHP 循环 - While 循环 循环执行代码块指定的次数,或者当指定的条件为真时循环执行代码块. PHP 循环 在您编写代码时,您经常需要让相同的代码块一次又一次地重复运行.我们可以在代码中使用循 ...

  6. PHP filegroup() 函数

    定义和用法 filegroup() 函数返回指定文件的组 ID. 如果成功,该函数返回指定文件所属组的 ID.如果失败,则返回 FALSE. 语法 filegroup(filename) 参数 描述 ...

  7. 4.13 省选模拟赛 传销组织 bitset 强连通分量 分块

    考试的时候昏了头 没算空间 这道题我爆零了.值得注意的是 一般认为bitset的空间是 int 的1/w倍 对于那m条边 无论如何构造 这m条关系都是存在的 题目其实是想让我们用这m条关系来计算给出的 ...

  8. QT学习笔记(day01)

    QT中的对象树 一定程度上简化了内存回收机制:当创建的对象 指定的父亲是由QObject或者Object派生的类时候,这个对象被加载到对象树上,当窗口关闭掉时候,树上的对象也都会被释放掉 信号和槽 通 ...

  9. iOS苹果美区 Apple ID 账号最新注册教程,iPhone用户务必收藏!

    编の语 前言 今天杀手宝宝出一个注册美区ID的教程,这是目前注册苹果美区ID最快的方法,所有人适合使用! 提の示 温馨提示: 所有内容均免费分享,部分资源来自于 网络,如与版权问题联系宝宝处理! 知道 ...

  10. 可能是Asp.net Core On host、 docker、kubernetes(K8s) 配置读取的最佳实践

    写在前面 为了不违反广告法,我竭尽全力,不过"最佳实践"确是标题党无疑,如果硬要说的话 只能是个人最佳实践. 问题引出 ​ 可能很多新手都会遇到同样的问题:我要我的Asp.net ...