Java核心技术 卷1-总结-18

news2024/11/26 8:35:19

Java核心技术 卷1-总结-18

  • 同步
    • Volatile域
    • final变量
    • 原子性
    • 死锁
    • 线程局部变量
    • 锁测试与超时
    • 读/写锁

同步

Volatile域

  • 多处理器的计算机能够暂时在寄存器或本地内存缓冲区中保存内存中的值。结果是,运行在不同处理器上的线程可能在同一个内存位置取到不同的值。
  • 编译器可以改变指令执行的顺序以使吞吐量最大化。这种顺序上的变化不会改变代码语义,但是编译器假定内存的值仅仅在代码中有显式的修改指令时才会改变。然而,内存的值可以被另一个线程改变。

如果使用锁来保护可以被多个线程访问的代码,那么可以不考虑这种问题。如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用同步。

volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
例如,假定一个对象有一个布尔标记done,它的值被一个线程设置却被另一个线程查询,可以使用锁:

private boolean done;
public synchronized boolean isDone() { return done; }
public synchronized void setDone() { done = true; }

使用锁并不合适,如果另一个线程已经对该对象加锁,isDonesetDone 方法可能阻塞。在这种情况下,将域声明为volatile是合理的:

private volatile boolean done;
public boolean isDone() { return done; }
public void setDone() { done = true; }

注意:Volatile变量不能提供原子性。例如,方法

public void flipDone() { done = !done; } // not atomic

不能确保翻转域中的值。不能保证读取、翻转和写入不被中断。

final变量

使用锁或volatile修饰符,可以从多个线程安全地读取一个域。还有一种情况可以安全地访问一个共享域,即这个域声明为final。考虑以下声明:

final Map<String, Double> accounts = new HashMap<>();

其他线程会在构造函数完成构造之后才看到这个accounts变量。如果不使用final,就不能保证其他线程看到的是accounts更新后的值,它们可能都只是看到null,而不是新构造的HashMap

注意:对这个映射表的操作并不是线程安全的。如果多个线程在读写这个映射表,仍然需要进行同步。

原子性

假设对共享变量除了赋值之外并不完成其他操作,那么可以将这些共享变量声明为volatile

java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。 例如,AtomicInteger类提供了方法incrementAndGetdecrementAndGet,它们分别以原子方式将一个整数自增或自减。例如,可以安全地生成一个数值序列,如下所示:

public static AtomicLong nextNumber = new AtomicLong();
// In some thread...
long id m nextNumber.incrementAndGet();

incrementAndGet方法以原子方式将 AtomicLong 自增,并返回自增后的值。也就是说,获得值、增1并设置然后生成新值的操作不会中断。可以保证即使是多个线程并发地访问同一个实例,也会计算并返回正确的值。

有很多方法可以以原子方式设置和增减值,不过,如果希望完成更复杂的更新,就必须使用compareAndSet方法。例如,假设希望跟踪不同线程观察的最大值。下面的代码是不可行的:

public static AtomicLong largest = new AtomicLong();// In some thread...
largest.set(Math.max(largest.get(), observed));//Error-race condition!

这个更新不是原子的。实际上,应当在一个循环中计算新值和使用compareAndSet:

do {
	oldvalue = largest.get();
	newValue = Math.max(oldValue,observed);
} while(!largest.compareAndSet(oldValue, newValue));

如果另一个线程也在更新largest,就可能阻止这个线程更新。这样一来,compareAndSet 会返回false,而不会设置新值。

如果有大量线程要访问相同的原子值,性能会大幅下降,因为乐观更新需要太多次重试。

死锁

锁和条件不能解决多线程中的所有问题。考虑下面的情况:

  1. 线程1和线程2分别向账户1和账户2转入大于自身余额的金额,由于余额都不足以进行转账,两个线程都无法执行下去。
账户1:$200 
账户2:$300
线程1:从账户1转移$300到账户2 
线程2:从账户2转移$400到账户1

在这里插入图片描述

如图所示,线程1和线程2都被阻塞了。因为账户1以及账户2中的余额都不足以进行转账,两个线程都无法执行下去。这样的状态称为死锁(deadlock)。

  1. 导致死锁的另一种途径是让第i个线程负责向第i个账户存钱,而不是从第i个账户取钱。这样一来,有可能将所有的线程都集中到一个账户上,每一个线程都试图从这个账户中取出大于该账户余额的钱。
  2. 还有一种很容易导致死锁的情况:将signalAll方法转换为signal,该程序最终会挂起。signalAll通知所有等待增加资金的线程,与此不同的是signal方法仅仅对一个线程解锁。如果该线程不能继续运行,所有的线程可能都被阻塞。

Java编程语言中没有任何东西可以避免或打破死锁现象。必须仔细设计程序,以确保不会出现死锁。

线程局部变量

使用ThreadLocal辅助类为各个线程提供各自的实例。 例如,SimpleDateFormat类不是线程安全的。假设有一个静态变量:

public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyy-MM-dd");

如果两个线程都执行以下操作:

String dateStamp = dateFormat.format(new Date());

datcFormat使用的内部数据结构可能会被并发的访问所破坏。可以使用同步,但开销很大;或者也可以在需要时构造一个局部SimpleDateFormat对象,但同样会造成较大的开销。

要为每个线程构造一个实例,可以使用以下代码:

public static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

要访问具体的格式化方法,可以调用:

String dateStamp = dateFormat.get().format(new Date());

在一个给定线程中首次调用get 时,会调用initialValue方法。在此之后,get方法会返回属于当前线程的那个实例。

在多个线程中生成随机数也存在类似的问题。java.util.Random类是线程安全的。但是如果多个线程需要等待一个共享的随机数生成器,会很低效。
可以使用ThreadLocal辅助类为各个线程提供一个单独的生成器,不过Java 还另外提供了一个便利类ThreadLocalRandom。只需要做以下调用:

int random = ThreadLocalRandom.current().nextInt(upperBound);

ThreadLocalRandom.current()调用会返回特定于当前线程的Random类实例。

 T get()

得到这个线程的当前值。如果是首次调用get,会调用initialize来得到这个值。

protected initialize()

应覆盖这个方法来提供一个初始值。默认情况下,这个方法返回null。

void set(T t)

为这个线程设置一个新值。

void remove()

删除对应这个线程的值。

static <S> ThreadLocal<S> withInitial(Supplier<? extends S>
supplier)

创建一个线程局部变量,其初始值通过调用给定的supplier生成。

锁测试与超时

线程在调用lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。tryLock方法试图申请一个锁,在成功获得锁后返回true,否则,立即返回false,线程可以立即离开去做其他事情。

if (myLock.tryLock()) {
	// now the thread owns the lock 
	try { ... }
	finally{myLock.unlock();}
} else
	// do something else

调用tryLock时,可以使用超时参数:

if (myLock.tryLock(100, TimeUnit.MILLISECONDS))...

TimeUnit是一个枚举类型,可以取的值包括SECONDSMILLISECONDSMICROSECONDSNANOSECONDS

lock 方法不能被中断。如果一个线程在等待获得一个锁时被中断,中断线程在获得锁之前一直处于阻塞状态。如果出现死锁,那么,lock方法就无法终止。

如果调用带有用超时参数的tryLock,那么如果线程在等待期间被中断,将抛出InterruptedException异常。这是一个非常有用的特性,因为允许程序打破死锁。

在等待一个条件时,也可以提供一个超时:

myCondition.await(100, TimeUnit.MILLISECONDS))

如果一个线程被另一个线程通过调用signalAllsignal激活,或者超时时限已达到,或者线程被中断,那么await方法将返回。

如果等待的线程被中断,await方法将抛出一个InterruptedException异常。

java.util.concurrent.locks.Lock 5.0

boolean tryLock()

尝试获得锁而没有发生阻塞;如果成功返回真。这个方法会抢夺可用的锁,即使该锁有公平加锁策略,即便其他线程已经等待很久也是如此。

boolean tryLock(long time, TimeUnit unit)

尝试获得锁,阻塞时间不会超过给定的值;如果成功返回true

void lockInterruptibly()

获得锁,但是会不确定地发生阻塞。如果线程被中断,抛出一个InterruptedException 异常。

读/写锁

java.util.concurrent.locks包定义了两个锁类:ReentrantLock类和ReentrantReadWriteLock类。如果很多线程从一个数据结构读取数据而很少线程修改其中数据的,在这种情况下,允许对读者线程共享访问是合适的。但是,写者线程依然必须是互斥访问的。
下面是使用读/写锁的必要步骤:
(1)构造一个ReentrantReadWriteLock对象:

private ReentrantReadWriteLock rwl = new ReentrantReadwriteLock();

(2)抽取读锁和写锁:

private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();

(3)对所有的获取方法加读锁:

public double getTotal Balance() {
	readLock.lock();
	try {...}
	finally { readLock.unlock();}
}

4)对所有的修改方法加写锁:

public void transfer(...) {
	writeLock.lock();
	try{...}
	finally { writeLock.unlock();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/459689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

建仓价和持仓价的应用:如何开仓如何持仓

建仓、持仓&#xff0c;是交易中绕不开的话题&#xff0c;没有建仓、持仓&#xff0c;何来建仓价、持仓价呢&#xff1f;所以这也是基础问题。不过作为市场形式的表现来说&#xff0c;建仓、持仓到后来的平仓贯彻始终&#xff0c;虽然是基础问题&#xff0c;也是后面登堂入室、…

【图数据库实践教程】Ubuntu22.04-Neo4j中文版安装及导入owl文件(通用教程)

文章目录 0. 环境准备0.1 静态配置IP&#xff1a;192.168.1.54&#xff0c;及网卡类型&#xff1a;NAT模式0.2 激活root用户&#xff1a;0.3 更改apt国内镜像源&#xff08;下载快&#xff09;0.4 关闭相关防火墙等保证网络顺畅 1. 相关软件安装1.1 安装好服务器内的相关软件1.…

FL Studio2023中文版数字音频工作站(DAW)软件

FL Studio21水果软件能支持制作各种音乐类型&#xff0c;除了最擅长的电子音乐&#xff0c;还可以任意创作流行音乐、古典音乐、民族音乐、乡村音乐、爵士乐等等&#xff0c;没有音乐类型的限制&#xff0c;让你的音乐突破想象力的限制。 FL Studio 2023中文版是数字音频工作站…

LINUX的系统管理与维护命令

文章目录 一、LINUX的系统管理与维护命令总结 一、LINUX的系统管理与维护命令 - Linux ls命令:显示指定工作目录下的内容 Linux pwd命令:显示当前工作目录 Linux cd命令:切换工作目录 Linux date命令:显示或设置系统时间 Linux su命令:切换用户 Linux clear命令:清除屏幕 Li…

5种简单快速的方法解除PDF文件密码保护

PDF 文件已经成为了我们日常工作、学习中广泛使用的文档格式之一。为了对重要的 PDF 文件进行保护&#xff0c;我们有时需要添加密码保护功能来防止未授权访问或修改。但是&#xff0c;如果您的 PDF 文件已经有了密码保护&#xff0c;而您需要快速访问和编辑它们&#xff0c;那…

机器人工程师与孔乙己文学

本文内容严格按创作模板发布&#xff1a; 孔乙已是鲁迅笔下人物&#xff0c;穷困流倒还穿着象征读书人的长衫&#xff0c;迁腐、麻木。最近&#xff0c;大家自我调佩是“当代孔乙己”&#xff0c;学历成为思想负担&#xff0c;找工作时高不成低不就。你可以从以下几个角度说说…

static_cast、dynamic_cast和reinterpret_cast区别和联系

其实网上相关的资料不少&#xff0c;但是能够说清楚明白这个问题的也不多。 于是&#xff0c;我尝试着问了一下AI&#xff0c;感觉回答还可以&#xff0c;但是需要更多的资料验证。 让我们先看看AI是怎么回答这个问题的。 static_cast、dynamic_cast和reinterpret_cast都是C中…

“SCSA-T学习导图+”系列:路由技术之OSPF入门

本期引言&#xff1a; 路由技术是网络环境中&#xff0c;为不同的节点传输数据提供传输路径的技术&#xff0c;企业网络的拓扑一般会比较复杂&#xff0c;不同的部门或者总部和分支可能处于不同的网段中&#xff0c;此时就需要使用路由协议来连接不同的网段&#xff0c;实现数…

Parker机电产品(运动控制/伺服电机/直线电机)在FPD行业应用

Parker新控制器-PAC PAC集高级逻辑控制&#xff0c;多轴运动&#xff0c;信号处理和webpublished可视化功能。 采用工业主流的EtherCAT运动控制协议&#xff0c;I/O扩展和第三方设备链接&#xff0c;结合应用开发软件PARKERAutomation Manager &#xff0c; PAC能为OEM需要的…

微信仿真平台的设计和实现(设计+源码)_kaic

摘要 现如今&#xff0c;科技的发展带动着环保方式的更新&#xff0c;Internet是一个不断的开展和不停的扩充数据潮流&#xff0c;有了它&#xff0c;我们可以快速、容易地在世界的任何角落进行沟通&#xff0c;获取更多的信息与资料。Internet可以提供大量信息资源和文案数据库…

临近五一,游玩地点想好了吗,Python帮你查找旅游景点的详细数据

前言 好不容易没有了疫情&#xff0c;三年整整三年&#xff0c;都要把我憋死了&#xff0c;想到去年暑假的时候&#xff0c;准备去厦门&#xff0c;攻略做好了&#xff0c;厦门疫情来了&#xff0c;想着转去济南也是这样&#xff0c;去三亚&#xff0c;结果收到好几万人都被留…

5G网络切片路由选择策略介绍

终端保存的NSSP(Network Slice Selection Policy)策略来源于网络侧。 NSSP规则是将应用程序匹配到S-NSSAI(Single network slice selection assistance information),并将应用程序绑定到现有PDU会话或发起新的PDU会话。 NSSP功能 NSSP的作用就是为应用程序选择S-NSSAI和…

HashMap如何解决哈希冲突

HashMap如何解决哈希冲突 Hash算法和Hash表Hash冲突解决哈希冲突的方法开放地址法链式寻址法再hash法建立公共溢出区 Hash算法和Hash表 Hash算法就是把任意长度的输入通过散列算法编程固定长度的输出。这个输出结果就是一个散列值。 Hash表又称为“散列表”&#xff0c;它是通…

LVS负载均衡群集部署——DR直接路由

目录 一、LVS-DR模式二、LVS-DR模式的特点三、LVS-DR中的ARP问题 二、LVS负载均衡群集-DR模式部署1.配置nfs共享&#xff08;192.168.154.10&#xff09;2.部署第一台nginx服务&#xff08;192.168.154.11&#xff09;3.部署第二台nginx服务&#xff08;192.168.154.12&#xf…

React入门学习

参考资料&#xff1a;https://bright-boy.gitee.io/technical-notes/#/react/React%E5%85%A5%E9%97%A8 https://github.com/xzlaptt/React react学习01: https://docs.qq.com/doc/DSG1jdUJtQ3FYR1V1 react学习02: https://docs.qq.com/doc/DSGhGZk9PUm1KVldv React简介 框架定…

滚动数组-动态规划之-不同路径 II_20230421

DP动态规划之-滚动数组 前言 在学习 不同路径II 的动态规划过程中&#xff0c;从介绍资料中了解到 滚动数组可以进一步降低动态规划解空间的复杂度&#xff0c;更高效利用计算机的储存空间。动态规划中的滚动数组究竟能发挥哪些作用&#xff0c;在常规的动态规划中&#xff0…

SpringBoot+Vue 前后端分离 微服务项目 打包部署全流程(原始部署/宝塔部署)

前端打包部署 前端项目为vue项目&#xff0c;使用vue admin template作为后台管理模板进行开发。 前端打包 了解开发环境、生产环境&#xff0c;修改生产环境的配置 在开发的时候&#xff0c;往往使用本地电脑进行开发。但是项目上线的时候&#xff0c;需要部署到云服务器中…

基于二阶锥规划(SOCP)松弛和线性离流的配电网规划(DNP)方法示例(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 配电网最优潮流 Optimal Power Flow, OPF) 问题是指在满足一定约束条件的情况下&#xff0c;通过控制配电网中的可控变量&…

内网Nexus代理docker-ce(yum) 源私有仓库 + 内网设备配置(centos)

配置docker-ce源 这里也是以阿里云的docker-ce源为例。 源地址为&#xff1a;https://mirrors.aliyun.com/docker-ce/linux/centos 1、在nexus上配置代理 Nexus上创建Blob Stores 创建Repositories 点击设置 - -> Repositories -- > Create repository --> yum(p…

分布式消息队列Kafka(二)- 生产者

1.生产者消息发送流程 &#xff08;1&#xff09;消息发送原理 ​ 在消息发送的过程中&#xff0c;涉及到了两个线程——main线程和 Sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator&#xff0c;Sender 线程不断…