java并发编程实战好难_(二) Java并发编程实战——线程安全性

1.1 什么是线程安全性?

要对线程安全性给出一个确切的定义是非常复杂的。定义越正式,就越复杂,不仅很难提供实际意义的指导建议,而且很难从直观上去理解。例如这样的描述“如果某个类可以在多个线程中安全地使用,那么它就是一个线程安全的类”。对于这种说法,虽然没有太多争议,但同样不会带来多大帮助。

在线程安全性的定义中,最核心的概念就是正确性。如果对线程安全性的定义是模糊的,那么就是因为缺乏对正确性的清晰定义。正确性的含义是,某个类的行为与其规范完全一致。

在良好的规范中,通常会定义各种不变性条件来约束对象的状态,以及定义各种后验条件来描述对象操作的结果。由于我们通常不会为类编写详细的规范,那么如何知道这些类是否正确呢?我们无法知道,但这并不妨碍我们对正确性的理解,因此我们将单线程的正确性近似定义为“所见即所知”。

在对“正确性”给出了一个较为清晰的定义后,就可以定义线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么称这个类是线程安全的。如果某个类在单线程环境中都不是正确的,那么它肯定不会是线程安全的。

无状态对象:无状态的含义是指与其它任何对象都没有共享的域。访问无状态对象的线程不会影响到另一个访问同一个对象的线程,因为两个线程之间没有共享状态,就好像它们在访问不同的实例。由于线程访问无状态对象的行为并不会影响其它线程中操作的正确性,因此无状态对象是线程安全的。

1.2 原子性

如果做一个“命中计数器”(Hit Counter)来记录访问请求数,一种直观的方法是增加一个long类型的域,并且每处理一个请求就将这个值加1。然而不幸的是,这样并不是线程安全的。因为counter++看上去只是一个操作,但是它并不是原子的。它包含的过程是“读取——修改——写入”这三个操作,并且每个操作的结果状态依赖于之前的状态。

如果该计数器被用来生成数值序列或者唯一的对象标识符,那么在多次调用中返回相同的值将导致严重的数据完整性问题。在并发编程中,这种由于不恰当的执行时序而导致不正确的结果是一种非常重要的情况,它有一个正式的名字:竞态条件(Race Condition)。

当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。换句话说,正确的运行结果取决于运气。最常见的竞态条件类型就是“先检查后执行(Check-Then-Act)”操作。

使用“先检查后执行”的一种常见情况就是延迟初始化,延迟初始化的目的是将对象的初始化操作推迟到实际被使用时才执行,同时要确保只被初始化一次。代码如下:

@NotThreadSafepublic classLazyInitRace{private ExpensiveObject instance = null;publicExpensiveObject getInstance(){if(instance == null){

instance= newExpensiveObject();

}returninstance;

}

}

在LazyInitRace中包含一个竞态条件,可能会破坏类的正确性。假定线程A和线程B同时执行getInstance。A获取的instance为空,因而创建一个新的ExpensiveObject;B同样判断instance时,结果为空,说明两次调用getInstance时可能会得到不同的结果。

复合操作:对于某些操作,需要以原子方式执行(或者说不可分割的操作)。要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其它线程使用这个变量,从而确保其它线程只能在操作完成之前或之后读取和修改状态。我们将“先检查后执行”和“读取-修改-写入”等操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。

1.3 保证正确性的方法

(1)线程安全类

为确保操作的原子性,有很多方法可以使用,这里使用一个现有的线程安全类:

@ThreadSafepublic class CountingFactorizer implementsServlet {private final AtomicLong count = new AtomicLong(0);public long getCount(){ returncount.get(); }public voidservice(ServletRequest req, ServletResponse resp){

BigInteger i=extractFromRequest(req);

BigInteger[] factors=factor(i);

count.incrementAndGet();

encodeIntoResponse(resp, factors);

}

}

count.incrementAndGet()与计数器状态一致,而且它是线程安全的,因此这里的Servlet也是线程安全的。

(2)加锁机制

当在Servlet中添加一个状态变量时,可以通过线程安全的对象来管理Servlet的状态以维护Servlet的线程安全性。但是如果想在Servlet中添加更多的状态,那么是否只需添加更多的线程安全状态变量就足够呢?答案是否定的。要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

内置锁

Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。

synchronized(lock){//访问或修改由锁保护的共享状态

}

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。Java的内置锁相当于一种互斥锁,这意味着最多只有一个线程能持有这种锁。

重入

当某个线程请求一个由其它线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果一个线程试图获得一个它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作粒度是“线程”,而不是“调用”。重入的一种实现方法是,为每个锁关联一个获取计算值和一个所有者线程。当计数值为0时,这个锁就被认为没有线程持有。当线程请求一个未被持有的锁时,JVM会记下锁的持有者,并且将获取计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应的递减。当计数值为0时,这个锁将被释放。

(3)活跃性与性能

如果不加考虑地使用锁,虽然能确保线程安全性,但付出的代价可能非常高。在一个方法上加synchronized锁,这样每次只有一个线程可以执行,这在负载过高的情况下将给用户带来糟糕的体验。如果系统中有多个CPU系统,那么当负载值很高时,仍然会有处理器处于空闲状态。

我们将这种Web应用程序称之为不良并发(Poor Concurrency)应用程序:可同时调用的数量,不仅受到可用处理资源的限制,还受到应用程序本身结构的限制。幸运的是,通过缩小同步代码块的作用范围,可以做到既确保并发行,同时又维护线程安全性。应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去,从而在这些操作的执行过程中,其它线程可以访问共享状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行和易用,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
### 回答1: 《Java并发编程实战》是一本经典的Java并发编程指南,由Brian Goetz等人撰写。这本书针对Java多线程并发编程的实践问题给出了详细的解决方案和最佳实践。 该书的主要内容包括:线程安全、对象的共享与发布、锁的优化、能与可伸缩、构建和组合对象、基础构建模块、任务执行、取消与关闭、线程池的使用、显式锁、构建自定义的同步工具等。 通过阅读这本书,读者可以了解Java中的各种并发问题,并学习如何设计和实现线程安全Java应用。该书引入了很多并发编程的常见问题,例如:竞态条件、死锁、活跃危险等,并提供了一些模式和技术来解决这些问题。 除了基础知识之外,该书还介绍了一些高级的并发编程概念,例如:并发集合、同步类、线程池等。这些内容将帮助读者更好地理解和利用Java并发编程能力。 总的来说,《Java并发编程实战》是一本权威而实用的Java并发编程指南,适合对多线程编程有一定了解的Java开发人员阅读。通过阅读这本书,读者可以更深入地理解Java并发编程的原理与应用,提高自己的并发编程能力。 ### 回答2: 《Java并发编程实战》是由美国计算机科学家布莱恩·戈策等人合著的一本书,是学习Java并发编程的经典教材。这本书系统地介绍了Java中的多线程、并发和并行编程的基本知识和应用。该书分为三个部分,分别是基础篇、高级主题篇和专题扩展篇。 在基础篇中,书中详细介绍了Java内存模型、线程安全、对象的共享、发布和逸出等概念。同时,还对Java中的锁、线程池、阻塞队列等常用并发工具进行了深入讲解,帮助读者理解并发编程的基本原理和机制。 高级主题篇则讨论了一些更加复杂的并发编程问题,如线程间的协作、线程间通信、并发集合类的使用和自定义的同步工具的设计等内容。该篇章通过讲解常见的并发问题和解决方案,提供了应对复杂并发场景的实践经验。 专题扩展篇主要讨论了一些与并发编程相关的主题,如并发问题的调试与测试、程序能调优等。这些内容能够帮助读者进一步提升对并发编程的理解和应用水平。 《Java并发编程实战》通过深入浅出的语言和大量实例,帮助读者掌握并发编程的基本概念和技术,为读者提供设计和编写高效多线程程序的实践经验。无论是Java初学者还是有一定经验的开发人员,都可以从中获得丰富的知识和实用的技巧,加速自己在并发编程领域的成长。 ### 回答3: 《Java并发编程实战》是一本经典的Java多线程编程指南,被广泛认可为学习并发编程的必读之作。本书由Brian Goetz等多位并发编程领域的专家合著,内容全面且深入浅出,适合从初学者到高级开发人员阅读。 该书分为四个部分,共16章。第一部分介绍了并发编程的基础知识,包括线程安全、对象的共享、对象组合等。第部分讲解了如何构建可复用的并发构件,包括线程安全、发布与初始化安全等。第三部分深入讨论了Java并发编程中常见的问题和挑战,例如活跃能与可伸缩等。第四部分则介绍了一些高级主题,如显式锁、原子变量和并发集合等。 书中包含了大量的示例代码和实践案例,可以帮助读者更好地理解并发编程的概念和技术。此外,本书还提供了一些最佳实践和经验教训,帮助读者避免常见的并发编程陷阱和错误。 《Java并发编程实战》从理论到实践的结合非常好,书中所介绍的内容都是经过实践验证的,具有很高的可靠和实用。无论是初学者还是有一定经验的开发人员,都可以从中获得实际应用的知识和经验。 综上所述,如果你想系统地学习Java并发编程,了解如何编写高效、可靠的多线程代码,那么《Java并发编程实战》是一本值得推荐的书籍。它可以帮助你深入理解并发编程的原理和技术,提高自己在并发编程领域的能力和水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值