关于多线程编程您不知道的 5 件事 有关高性能线程处理的微妙之处

虽然很少有 Java™ 开发人员能够忽视多线程编程和支持它的 Java 平台库,更少有人有时间深入研究线程。相反地,我们临时学习线程,在需要时向我们的工具箱添加新的技巧和技术。以这种方式构建和运行适当的应用程序是可行的,但是您可以做的不止这些。理解 Java 编译器的线程处理特性和 JVM 将有助于您编写更高效、性能更好的 Java 代码。

在这期的 5 件事 系列 中,我将通过同步方法、volatile 变量和原子类介绍多线程编程的一些更隐晦的方面。我的讨论特别关注于这些构建如何与 JVM 和 Java 编译器交互,以及不同的交互如何影响 Java 应用程序的性能。

1. 同步方法或同步代码块?

您可能偶尔会思考是否要同步化这个方法调用,还是只同步化该方法的线程安全子集。在这些情况下,知道 Java 编译器何时将源代码转化为字节代码会很有用,它处理同步方法和同步代码块的方式完全不同。

当 JVM 执行一个同步方法时,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

另一方面,同步化一个方法块会越过 JVM 对获取对象锁和异常处理的内置支持,要求以字节代码显式写入功能。如果您使用同步方法读取一个方法的字节代码,就会看到有十几个额外的操作用于管理这个功能。清单 1 展示用于生成同步方法和同步代码块的调用:


清单 1. 两种同步化方法

				
package com.geekcap;public class SynchronizationExample {private int i;public synchronized int synchronizedMethodGet() {return i;}public int synchronizedBlockGet() {synchronized( this ) {return i;}}
}

synchronizedMethodGet() 方法生成以下字节代码:

	0:	aload_01:	getfield2:	nop3:	iconst_m14:	ireturn

这里是来自 synchronizedBlockGet() 方法的字节代码:

	0:	aload_01:	dup2:	astore_13:	monitorenter4:	aload_05:	getfield6:	nop7:	iconst_m18:	aload_19:	monitorexit10:	ireturn11:	astore_212:	aload_113:	monitorexit14:	aload_215:	athrow

创建同步代码块产生了 16 行的字节码,而创建同步方法仅产生了 5 行。

2. ThreadLocal 变量

如果您想为一个类的所有实例维持一个变量的实例,将会用到静态类成员变量。如果您想以线程为单位维持一个变量的实例,将会用到线程局部变量。ThreadLocal 变量与常规变量的不同之处在于,每个线程都有其各自初始化的变量实例,这通过 get() 或 set() 方法予以评估。

比方说您在开发一个多线程代码跟踪器,其目标是通过您的代码惟一标识每个线程的路径。挑战在于,您需要跨多个线程协调多个类中的多个方法。如果没有 ThreadLocal,这会是一个复杂的问题。当一个线程开始执行时,它需要生成一个惟一的令牌来在跟踪器中识别它,然后将这个惟一的令牌传递给跟踪中的每个方法。

使用 ThreadLocal,事情就变得简单多了。线程在开始执行时初始化线程局部变量,然后通过每个类的每个方法访问它,保证变量将仅为当前执行的线程托管跟踪信息。在执行完成之后,线程可以将其特定的踪迹传递给一个负责维护所有跟踪的管理对象。

当您需要以线程为单位存储变量实例时,使用 ThreadLocal 很有意义。

3. Volatile 变量

我估计,大约有一半的 Java 开发人员知道 Java 语言包含 volatile 关键字。当然,其中只有 10% 知道它的确切含义,有更少的人知道如何有效使用它。简言之,使用 volatile 关键字识别一个变量,意味着这个变量的值会被不同的线程修改。要完全理解 volatile关键字的作用,首先应当理解线程如何处理非易失性变量。

为了提高性能,Java 语言规范允许 JRE 在引用变量的每个线程中维护该变量的一个本地副本。您可以将变量的这些 “线程局部” 副本看作是与缓存类似,在每次线程需要访问变量的值时帮助它避免检查主存储器。

不过看看在下面场景中会发生什么:两个线程启动,第一个线程将变量 A 读取为 5,第二个线程将变量 A 读取为 10。如果变量 A 从 5 变为 10,第一个线程将不会知道这个变化,因此会拥有错误的变量 A 的值。但是如果将变量 A 标记为 volatile,那么不管线程何时读取 A 的值,它都会回头查阅 A 的原版拷贝并读取当前值。

如果应用程序中的变量将不发生变化,那么一个线程局部缓存比较行得通。不然,知道 volatile 关键字能为您做什么会很有帮助。

4. 易失性变量与同步化

如果一个变量被声明为 volatile,这意味着它预计会由多个线程修改。当然,您会希望 JRE 会为易失性变量施加某种形式的同步。幸运的是,JRE 在访问易失性变量时确实隐式地提供同步,但是有一条重要提醒:读取易失性变量是同步的,写入易失性变量也是同步的,但非原子操作不同步。

这表示下面的代码不是线程安全的:

myVolatileVar++;

上一条语句也可写成:

int temp = 0;
synchronize( myVolatileVar ) {temp = myVolatileVar;
}temp++;synchronize( myVolatileVar ) {myVolatileVar = temp;
}

换言之,如果一个易失性变量得到更新,这样其值就会在底层被读取、修改并分配一个新值,结果将是一个在两个同步操作之间执行的非线程安全操作。然后您可以决定是使用同步化还是依赖于 JRE 的支持来自动同步易失性变量。更好的方法取决于您的用例:如果分配给易失性变量的值取决于当前值(比如在一个递增操作期间),要想该操作是线程安全的,那么您必须使用同步化。

5. 原子字段更新程序

在一个多线程环境中递增或递减一个原语类型时,使用在 java.util.concurrent.atomic 包中找到的其中一个新原子类比编写自己的同步代码块要好得多。原子类确保某些操作以线程安全方式被执行,比如递增和递减一个值,更新一个值,添加一个值。原子类列表包括 AtomicIntegerAtomicBooleanAtomicLongAtomicIntegerArray 等等。

使用原子类的难题在于,所有类操作,包括 getset 和一系列 get-set 操作是以原子态呈现的。这表示,不修改原子变量值的 read和 write 操作是同步的,不仅仅是重要的 read-update-write 操作。如果您希望对同步代码的部署进行更多细粒度控制,那么解决方案就是使用一个原子字段更新程序。

使用原子更新

像 AtomicIntegerFieldUpdaterAtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater 之类的原子字段更新程序基本上是应用于易失性字段的封装器。Java 类库在内部使用它们。虽然它们没有在应用程序代码中得到广泛使用,但是也没有不能使用它们的理由。

清单 2 展示一个有关类的示例,该类使用原子更新来更改某人正在读取的书目:


清单 2. Book 类

				
package com.geeckap.atomicexample;public class Book
{private String name;public Book(){}public Book( String name ){this.name = name;}public String getName(){return name;}public void setName( String name ){this.name = name;}
}

Book 类仅是一个 POJO(Java 原生类对象),拥有一个单一字段:name。


清单 3. MyObject 类

				
package com.geeckap.atomicexample;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/**** @author shaines*/
public class MyObject
{private volatile Book whatImReading;private static final AtomicReferenceFieldUpdater<MyObject,Book> updater =AtomicReferenceFieldUpdater.newUpdater( MyObject.class, Book.class, "whatImReading" );public Book getWhatImReading(){return whatImReading;}public void setWhatImReading( Book whatImReading ){//this.whatImReading = whatImReading;updater.compareAndSet( this, this.whatImReading, whatImReading );}
}

正如您所期望的,清单 3 中的 MyObject 类通过 get 和 set 方法公开其 whatAmIReading 属性,但是 set 方法所做的有点不同。它不仅仅将其内部 Book 引用分配给指定的 Book(这将使用 清单 3 中注释出的代码来完成),而是使用一个AtomicReferenceFieldUpdater

AtomicReferenceFieldUpdater

AtomicReferenceFieldUpdater 的 Javadoc 将其定义为:

对指定类的指定易失性引用字段启用原子更新的一个基于映像的实用程序。该类旨在用于这样的一个原子数据结构中:即同一节点的若干引用字段独立地得到原子更新。

在 清单 3 中,AtomicReferenceFieldUpdater 由一个对其静态 newUpdater 方法的调用创建,该方法接受三个参数:

  • 包含字段的对象的类(在本例中为 MyObject
  • 将得到原子更新的对象的类(在本例中是 Book
  • 将经过原子更新的字段的名称

这里真正的价值在于,getWhatImReading 方法未经任何形式的同步便被执行,而 setWhatImReading 是作为一个原子操作执行的。

清单 4 展示如何使用 setWhatImReading() 方法并断定值的变动是正确的:


清单 4. 演习原子更新的测试用例

				
package com.geeckap.atomicexample;import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;public class AtomicExampleTest
{private MyObject obj;@Beforepublic void setUp(){obj = new MyObject();obj.setWhatImReading( new Book( "Java 2 From Scratch" ) );}@Testpublic void testUpdate(){obj.setWhatImReading( new Book( "Pro Java EE 5 Performance Management and Optimization" ) );Assert.assertEquals( "Incorrect book name", "Pro Java EE 5 Performance Management and Optimization", obj.getWhatImReading().getName() );}}

参阅 参考资料 了解有关原子类的更多信息。

结束语

多线程编程永远充满了挑战,但是随着 Java 平台的演变,它获得了简化一些多线程编程任务的支持。在本文中,我讨论了关于在 Java 平台上编写多线程应用程序您可能不知道的 5 件事,包括同步化方法与同步化代码块之间的不同,为每个线程存储运用ThreadLocal 变量的价值,被广泛误解的 volatile 关键字(包括依赖于 volatile 满足同步化需求的危险),以及对原子类的错杂之处的一个简要介绍。参见 参考资料 部分了解更多内容。

参考资料

学习

  • 您不知道的 5 件事 ... :在本系列中发现关于 Java 平台您不知道的事情,本系列致力于将 Java 技术琐事变成有用的编程技巧。

  • “Code Tracing”(Steven Haines,InformIT,2010 年 8 月):了解使用 ThreadLocal 变量进行代码跟踪的更多内容。

  • “Java 字节码:了解字节码使你成为一个更好的程序员”(Peter Haggar,developerWorks,2001 年 7 月):一本介绍字节码次要领域的教程,包含展示同步方法和同步代码块之间区别的一个较早的例子。

  • Java 理论与实践:流行的原子”(Brian Goetz,developerWorks,2004 年 11 月):解释原子类如何支持用 Java 语言开发高度可伸缩的非阻塞算法。

  • Java 理论与实践: 并发在一定程度上使一切变得简单”(Brian Goetz,developerWorks,2002 年 11 月): 通过java.util.concurrent 包来为您提供指导。

  • 您不知道的 5 件事 ... java.util.concurrent,第 1 部分”(Ted Neward,developerWorks,2010 年 5 月):了解 5 个并发集合类,为您的并发编程需求改进标准集合类。

  • developerWorks Java technology 专区:这里有数百篇关于 Java 编程各个方面的文章。

讨论

  • 加入 developerWorks 中文社区。

关于作者

Steven Haines 是 ioko 的一名技术架构师,也是 GeekCap Inc 的创始人。在 Java 编程和性能分析方面,他写过 3 本书,以及上百篇文章和十几个白皮书。Steven 还在行业会议上发表演讲,比如 JBoss World 和 STPCon,而且他曾在加里佛尼亚大学欧文分校和 Learning Tree 大学教过 Java 编程,他居住在佛罗里达州奥兰多市。

建议

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

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

相关文章

Ubuntu作为服务器其tomcat被远程访问问题

根据我的Linux版本tomcat下载及安装安装好tomcat之后&#xff0c;但是还不能直接访问我 ubuntu 服务器的公网 ip&#xff0c; 即http://xxx.xxx.xxx.xxx:8080访问不到 Apache tomcat主页&#xff0c;还需要进行配置。 一、在路由器管理页面配置转发规则 如果你和我一样&#…

c#界面鼠标拖动

之前做过拖动&#xff0c;找了两种方法&#xff1a; 1、 private Point myPoint; private void Form1_MouseMove(object sender, MouseEventArgs e) { if (e.Button MouseButtons.Left) { Point myPosition Co…

没有已启用的仓库。 执行 “yum repolist all“ 查看您拥有的仓库。

问题描述&#xff1a; yum是RPM的前端程序&#xff0c;对于多软件安装时非常好用&#xff0c;在虚拟机中想要安装什么软件都可以使用yum install 软件来快速调用仓库安装&#xff0c;但是我初次使用这个命令的时候一直报错&#xff0c;显示 没有已启用的仓库。 执行 “yum rep…

解决ubuntu上网慢

Ubuntu上网慢?没错!不要再怀疑&#xff0c;Ubuntu上网的确要比Windows下慢。慢在哪里呢?在Ubuntu 9.10下使用Firefox浏览网页的时候你会发现&#xff0c;左下角的状态栏里&#xff0c;长时间的显示&#xff1a;Looking up xxxx.com.... 或者 Connecting to xxxx.com 的信息。…

最老程序员创业札记:全文检索、数据挖掘、推荐引擎应用33

快刀斩乱麻第二天一大早&#xff0c;吴言就准备去找老乡打听梁秀娟的行踪&#xff0c;虽然他从心里觉得小A和梁秀娟最近的做法都不对&#xff0c;但是他说不清为什么&#xff0c;也不愿意他们这样闹下去&#xff0c;到头来两败俱伤。在老乡之中&#xff0c;吴言首先想到的是自己…

Linux下安装Redis

下载安装包 Linux版本下载地址&#xff1a; Redis中文官方网站&#xff1a;http://www.redis.cn/ 点击箭头处即可下载tar.gz文件。 安装 1、上传文件到linux服务器上 我是使用Xshell远程安装到linux服务器&#xff0c;所以先使用xftp把安装包上传到服务器指定目录下 2、…

windows安装双JDK并实现版本切换

我本来只有一个JDK11&#xff0c;后来因为要用到jdk8&#xff0c;但是我又不想卸载掉11&#xff0c;于是想到了jdk共存&#xff0c;根据百度经验操作作了如下一通操作&#xff1a; 1、删除java.exe,javac.exe,javaw.exe 首先&#xff0c;jdk11会自动在 C:\Program Files\Comm…

三大最被低估的安全技术 用户数量决定一切

以下就是今天安全业界普遍人们被低估的技术。由于有些安全专业人士认为很神奇的工具可能在别人看来是预算上的浪费&#xff0c;所以如果一种技术同时出现在被高估和低估的名单里也就不足为奇了。 白名单 应用安全是一些企业越来越担心的问题&#xff0c;因为企业和个人的应用越…

Jedis使用测试——连接本地及远程的Redis

我们要使用Java来操作Redis&#xff0c;就要学会使用Jedis 一、什么是jedis 是Redis官方推荐的java连接开发工具&#xff01;使用Java操作Redis 中间件!如果你要使用java操作redis&#xff0c;那么一定要对Jedis十分的熟悉! 二、测试 新建一个空的工程&#xff0c;然后新建一…

NetBeans IDE 7.1 Window Layout Designer

http://blogs.oracle.com/geertjan/entry/netbeans_ide_7_1_window——————————————————————————————————————————————————————————————————In 7.1, youll see this in the New File dialog: I.e., as you ca…

org.springframework.data.redis.serializer.SerializationException: Cannot serialize;

错误&#xff1a; . ____ _ __ _ _/\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | _ | _| | _ \/ _ | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) ) |____| .__|_| |_|_| |_\__, | / / / /|_||___//_/_/_/:: Spring Boot :: …

poj2008

题意&#xff1a;给定一些点的坐标(hi,wi)&#xff0c;要选出一个点集&#xff0c;使得集合所包含的点数最多&#xff0c;且符合A*(H-h) B*(W-w) < C&#xff0c;h为集合中最小h&#xff0c;w为点集中最小w。 分析&#xff1a;我们是要找到这样的点集&#xff0c;hi>h&a…

Git右键没有Git Bash Here的解决办法

好长一段时间没有使用Git了&#xff0c;今天想用git在码云上下载东西&#xff0c;但是突然发现右键没有Git Bash Here了&#xff0c;然后在网上搜索了一番&#xff0c;然后解决了。 解决方法&#xff1a; winR 打开运行小窗口&#xff0c;在 “ 运行 ” 中输入‘ regedit ’&…

随想系列_4_从电影《可可西里》说起

今天&#xff0c;国庆节的第一天&#xff0c;感觉不到放假的轻松&#xff0c;反而更加觉得心情沉闷。 无聊&#xff0c;生活的主旋律&#xff0c;一直如此&#xff1b;死气沉沉&#xff0c;感觉不到活力&#xff1b;每天做的事空泛而无趣&#xff1b;没有任何意义&#xff0c;没…

对可重入锁和不可重入锁的理解

可重入锁&#xff1a;ReentrantLock 在学JUC的时候&#xff0c;听到可重入锁这个词&#xff0c;不理解它的概念&#xff0c;网上搜索一番&#xff0c;还是有点迷糊&#xff0c;所以自己再来做一下笔记&#xff0c;理一理思路。 一、锁是什么&#xff1f; 我们这里提到的锁&am…

Windows Phone 7开发一月谈(3)

如何获知手机与PC相连Spb Shell 技术研究windows mobile 6.0 C# 如何获得 SIM卡 ICCID &#xff1f;mobile上如何掉用API来挂掉电话关于全屏程序下输入法的显示问个获取主机IP地址的问题&#xff1f;在设置-电话界面中铃声播放问题怎样获取TAPI的LINECALLSTATE_RINGBACK消息Dir…

Synchronized 和 Lock 区别

Synchronized 和 Lock 区别 Synchronized 是内置的Java关键字&#xff0c; Lock 是一个Java类Synchronized 无法判断获取锁的状态&#xff0c;Lock 可以判断是否获取到了锁Synchronized 会自动释放锁&#xff0c;lock 必须要手动释放锁&#xff01;如果不释放锁&#xff0c;会…

对HashMap数据结构的理解——加载因子和初始容量

先看源码&#xff1a; 解释一下位移运算&#xff1a; 1<<4 是位移运算的表示&#xff0c;为十进制16 1的二进制表示&#xff1a;1 左移4位之后的二进制表示为B&#xff08;10000&#xff09; D&#xff08;16&#xff09; 更简单的计算方法就是 1<< n 等效于 1 乘…

objective-c 使用NSNumber 将int float long等数据类型加入到数组或字典中

objective-c 使用NSNumber 将int float long等数据类型加入到数组或字典中设置值和取值如下代码&#xff1a;NSNumber *number[NSNumber numberWithInt:45]; NSLog("NSNumber %d",[number intValue]);

远程服务器电脑的设置

1、因为我要远程的电脑已经被人家设置过了远程了&#xff0c;而他已经将远程的端口改为4816&#xff0c;所以我一直以为是3389端口&#xff0c;上网搜索了很久都没有发现什么问题出现在哪&#xff1f;命令环境下查看端口状态都还是关着的&#xff0c;没有打开&#xff0c;远程一…