IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java并发编程实战读书笔记 -> 正文阅读

[Java知识库]Java并发编程实战读书笔记

本文记录了阅读该书中本人认为比较有收获,比较重要的知识点。其中有一些内容加了一些自己的理解。

并发编程的挑战

上下文切换

上下文切换发生在同一个cpu中,cpu通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时可以再加载这个状态。任务从保存到再加载就是一次上下文切换因此多线程并不一定快,因为线程有创建和上下文切换的开销。

如何减少上下文切换

  1. 尽量少使用锁,尽量使用无锁并发编程。多线程进程锁时,会引起上下文切换,如councurrentHashmap对链表进行分段锁,减少了线程的锁住的时间。
  2. CAS算法。CAS更新,不需要加锁
  3. 使用最少线程。避免创建不需要的线程
  4. 协程。协程并不需要上下文

如何避免死锁

  1. 避免一个线程同时获取多个锁
  2. 尝试使用定时锁lock.tryLock(timeout)
  3. 顺序加锁

Java并发机制的底层实现原理

volatile的使用

violate如何保证可见性?

X86处理器下,volatile变量修饰的共享变量进行写操作时,汇编代码会 多出 lock 前缀指令

lock前缀的作用:

  1. 将当前处理器的缓存行的数据写会系统内存
  2. 这个写回内存的操作会在其他CPU里缓存了该内存地址的数据无效。( 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期)。

synchronized的实现原理与应用

JVM基于进入和对象的Monitor对象来实现同步,任何对一个都有一个monitor与之关联,并且一个monitor被持有后,他将处于锁定状态。monitorenter指令在编译后插入到同步代码块的开始位置,而moniterexit是插入到方法结束处和异常处。

synchronized作用域

  1. 对于普通同步方法,锁是当前实例对象
  2. 对于静态同步方法,锁是当前类的class对象
  3. 对于同步方法块,锁是synchronized括号里配置的对象

Java对象头

java对象的头的 Mark Word标志位会随着锁标志位的变化而变化。
在这里插入图片描述

锁升级

synchronized的无锁状态、偏向锁、轻量级锁、重量级锁。

偏向锁

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,以后该线程在进入和退出同步块时不需要cas操作来加锁和解锁,只需要简单测试一下对象头的mark word里是否存储着指向当前线程的偏向锁。
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有锁的线程才会释放锁。

Java 6 7里是默认使用的,但是要启动几秒钟之后才激活,若有必要可以使用JVM参数来关闭延迟,-XX:BIasedLockingStartUpDelay=0。JVM关闭偏向锁,-XX:-UseBiasedLocking = false。

轻量级锁

使用CAS将对象头Mark Word替换为指向锁记录的指针,如果成功,则当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁。
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),就会升级为重量级锁。重量级锁不会恢复到轻量级锁。当锁处于重量级锁,其他线程视图获取锁时,都会被阻塞住。

原子操作的实现原理

java中通过循环CAS的方式实现原子操作。
通过CAS实现原理
java中使用Unsafe包下的compareAndSet来实现的,该命令则是CMPXCHG指令加lock来实现的(和violate相同)。

cas实现锁的三大问题

1、ABA问题。通过AtomicStampedReference来解决ABA问题,检查值和引用
2、循环时间开销大
3、只能保证一个共享变量的原子操作。AtomicReference类保证对象的原子性

Java内存模型

在这里插入图片描述

happens-before原则

如果一个操作的执行的结果需要对另一个操作可见,那么这两个操作之间必须要有happens-before关系

violate的内存语义

理解violate的可见性和单个变量的可见性(书38页代码)

  • 可见性:对一个violate的读,总是能看到(任意线程)对这个volatile变量最后的写入
  • 原子性:对任意单个violate变量的读/写操作具有原子性,但类似于volatile++这种复合操作不具有原子性

violate内存语义的实现

重排序分为编译器重排序和处理器重排序,为了实现violate内存语义,JMM会分别限制这两个类型的重排序类型。
为了实现volatile的内存语义,编译器在生成字节码时,会在执行序列中插入内存屏障来禁止特定的处理器重排序。

锁的内存语义

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中,当前线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使被监视器保护的临界区代码必须从主内存中读取共享变量。

锁释放与violate写有相同的内存含义,锁获取与violate的读具有相同的内存含义

final的内存语义

(暂时没总结)

双重检查锁定与延迟初始化

单例模式中的延迟加载初始化方法中使用。
问题:

instance = new Singleton();
创建了一个对象,可以分解为如下三行代码:
memory = allocate(); //1.分配对象的内存空间
ctorInstance(memory)//2.初始化对象
instance = memory; //3. 设置instance指向刚分配的内存
  1. 基于violate关键字实现

2和3会有可能出现多排序,因此使用violate关键字,happens-before规则限制这个重排序。

public class SafeDoubleCheckedLocking{
	private volatile static Instance instance;
	public static Insatance getInstance(){
		if(instance == null){
			synchronized(SafeDoubleCheckedLocking.clss){
				if(instance == null){
					instance = new Instance()// instance为violate防止了重排序
				}
			}
		}
	} 
}
  1. 基于静态初始化机制来实现
    JVM在类的初始化阶段,会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁,这个锁可以同步多个线程对同一个类的初始化。
public class InstanceFactory{
	private static class InstanceHolder{
		public static Instance instance = new Instance();
	}
	public static Instance getInstance(){
		return InstanceHolder.instance; 
	}
}

Java中的锁

队列同步器

队列同步器AbstractQueuedSynchronizer是用来构建锁或其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
同步器的主要方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,State。使用compareAndSetState,使用CAS设置当前状态,该方法能够保证状态设置的原子性。
在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中,并在队列中进行自旋;移除队列的条件是前驱节点为头节点且成功获取了同步状态

ReentrantLock

reentrantLock是可以重进入的公平锁(也可以实现不公平),重进入表示该锁能够支持一个线程对资源的重复加锁,公平锁表示FIFO的获取锁。

非公平锁会使得线程“饥饿”,但是为什么它又被设定为默认实现方式呢?如果把每次不同的线程获取到锁定义为1次切换,非公平锁明显可以减少上下文切换的开销。

读写锁

(暂时没有看)

Condition接口

与jvm的 wait notify 一样的用法

Java中的并发工具类

CountDownLatch

其实就是对join()方法的增强

CyclicBarrier

其实和countdownlatch差不多,只是可以多次使用

Semaphore

信号量 用来做流量控制
比如有30个线程,只有10个线程可以运行

Semaphore s = new Semsphotre(10);
a.acquire() //若运行的线程小于10 则可以运行,否则堵塞
a.release() //释放

Java 线程池

利用线程池的优势:

1、降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

  • 创建线程池:

可以通过ThreadPoolExecutor来创建一个线程池。

在这里插入图片描述
创建线程池需要的几个参数:

corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。

SynchronousQueue:一个不存储元素的阻塞队列。

PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

JDK1.5提供的四种策略:

AbortPolicy:直接抛出异常。

CallerRunsPolicy:只用调用者所在线程来运行任务。

DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

DiscardPolicy:不处理,丢弃掉。

当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。

keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

用execuse()提交任务,退出用shutdown()

Java的四种线程池及代码

FixedThreadPool

FixedThreadPool 的队列大小是Integer.MAX_VALUE,核心线程池大小就为该线程池最大的大小。KeepAliveTime = 0.

SingleThreadExecutor

FixedThreadPool 的队列大小是Integer.MAX_VALUE,8核心线程池大小为1,最大线程数也为1。只有一个线程的线程池???

CachedThreadPool

CachedThreadPool是一个根据需要创建新线程的线程池,corePoolSize设置为0,maximumPoolSize被设置为Integer.MAX_VALUE,是无界的。keepAliveTime设置为60L,意味着空闲线程等待新任务最长时间为60秒。使用有容量的SynchonousQueue作为线程池的工作队列,极端情况下,会因为创建过多线程而耗尽CPU内存资源。

scheduledThreadPoolExecutor

该等待队列是根据最小time排列的一个堆,每次取出一个堆顶元素执行,执行完之后给该元素设定下一个执行时间,最后放回到待执行队列中。
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-05 17:11:57  更:2021-08-05 17:16:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/12 20:25:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码