编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

您不知道的多线程编程 多线程编程教程

gowuye 2024-04-02 10:20 5 浏览 0 评论

有关高性能线程处理的微妙之处

虽然很少有 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_0

1: getfield

2: nop

3: iconst_m1

4: ireturn

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

0: aload_0

1: dup

2: astore_1

3: monitorenter

4: aload_0

5: getfield

6: nop

7: iconst_m1

8: aload_1

9: monitorexit

10: ireturn

11: astore_2

12: aload_1

13: monitorexit

14: aload_2

15: 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 包中找到的其中一个新原子类比编写自己的同步代码块要好得多。原子类确保某些操作以线程安全方式被执行,比如递增和递减一个值,更新一个值,添加一个值。原子类列表包括 AtomicInteger、AtomicBoolean、AtomicLong、AtomicIntegerArray 等等。

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

使用原子更新

像 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 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;

@Before

public void setUp()

{

obj = new MyObject();

obj.setWhatImReading( new Book( "Java 2 From Scratch" ) );

}

@Test

public 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 满足同步化需求的危险),以及对原子类的错杂之处的一个简要介绍。参见 参考资料 部分了解更多内容。

相关推荐

PHPMailer远程命令执行漏洞分析

摘要:PHPMailer是一个强大的PHP编写的邮件发送类,但近日被爆出远程命令执行漏洞,该漏洞实际上是什么,有何种影响,本文对该漏洞进行了分析及验证方法,并给出防护方案。0x00漏洞概要PHPMa...

「安全漏洞」DedeCMS-5.8.1 SSTI模板注入导致RCE

漏洞类型SSTIRCE利用条件影响范围应用漏洞概述2021年9月30日,国外安全研究人员StevenSeeley披露了最新的DedeCMS版本中存在的一处SQL注入漏洞以及一处SSTI导致的RCE...

回顾使用PHP原生发送电子邮件(终)文件附件

FileAttachments文件附件Fileattachmentsworkjustlikemixedemail,exceptthatadifferentcontenttyp...

php-fpm.conf &amp; php.ini 安全优化实践

0x01关于php其历史相对已经比较久远了,这里也就不废话了,属弱类型中一种解释型语言除了web开发以及写些简单的exp,暂未发现其它牛逼用途,暂以中小型web站点开发为主另外,低版本的php自身...

linux 安全配置 ossec 开源检测

linux安全配置ossec开源检测一:介绍主要功能有日志分析、完整性检查、rootkit检测、基于时间的警报和主动响应。除了具有入侵检测系统功能外,它还一般被用在SEM/SIM(安全事件管理(...

PHP使用PHPMailer发送验证码邮件的方法与调用逻辑

首先我们需要下载PHPMailer:https://github.com/PHPMailer/PHPMailer一般情况下我们只需要压缩包中的src文件夹中的文件,并保存至根目录即可:设置一个文件,如...

回顾使用PHP原生发送电子邮件(一)

IwishIcouldremembertheveryfirstemailmessageIeversent.Unfortunately,thetruthisthatI...

PHPMAILER实现PHP发邮件功能php实例

这篇文章主要为大家详细介绍了PHPMAILER实现PHP发邮件功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下本文实例为大家分享了PHPMAILER实现PHP发邮件功能的具体代码,供大家参考,具...

500道网络安全面试题集锦(附答案)

本篇文章内容为网络安全各个方向涉及的面试题,星数越多代表问题出现的几率越大,但是无论如何都无法覆盖所有的面试问题,更多的还是希望由点达面,查漏补缺,然后祝各位前程似锦,都能找到自己满意的工作!一、We...

网站放家里,随处看电影「Apache+php+ssl 安装和配置」

  使用5G网络,随处都可以看到放自己家里电脑的视频。这个功能很容易实现,不需要太多的专业知识,也不需要额外花钱。如果确实需要,最多花不到两百块钱买一台旧电脑放家里,做个网站,就能解决全部问题,Fre...

Windows2008中 Magic Winmail Server提权

MagicWinmailServer是安全易用全功能的邮件服务器软件,不仅支持SMTP/POP3/IMAP/Webmail/LDAP(公共地址簿)/多域/发信认证/反垃圾邮件/邮件过滤/邮件组...

利用PHPmailer发送邮件

早上帮朋友做一个收集客户联系方式的页面,要求能实时推送信息给管理员。刚开始想到做后台管理,因为时间紧,做后台是赶不上了。想过通过短信发送,成本太高,否决了。。。灵机一动,客户提交时直接把信息发送到邮箱...

phpmailer发送邮件

phpmailer发送邮件PHP内置的mail函数使用起来不够方便,另外受其他语言的影响,博主更偏好面向对象的包管理模式,因此phpmailer成为了我用PHP发送邮件的首选,这里分享给大家。库导入这...

PHP初级教程:读取输入

PHP读取输入:Form:?formaction="welcome.php"method="post">Name:(inputtype="text...

php filter 验证Email,Url,Ip格式

今天发现一个非常好用的函数东西,filter过滤器,用于验证和过滤来自非安全来源的数据,比如用户的输入。验证Email:$email='1234567@qq.com';if(!filter_v...

取消回复欢迎 发表评论: